From d89d28e65bde37e7de701c7ae32c70667c438e6a Mon Sep 17 00:00:00 2001 From: lipengzha Date: Thu, 1 Dec 2022 09:42:22 +0800 Subject: [PATCH 01/81] fix 4.27 error --- .../Private/ShaderPatch/FlibShaderCodeLibraryHelper.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/FlibShaderCodeLibraryHelper.cpp b/HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/FlibShaderCodeLibraryHelper.cpp index e09b3613..41872f34 100644 --- a/HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/FlibShaderCodeLibraryHelper.cpp +++ b/HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/FlibShaderCodeLibraryHelper.cpp @@ -120,10 +120,10 @@ bool UFlibShaderCodeLibraryHelper::SaveShaderLibrary(const ITargetPlatform* Targ #if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 25 #if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 26 FString ErrorString; - bool bOutHasData; + bSaved = SHADER_COOKER_CLASS::SaveShaderLibraryWithoutChunking(TargetPlatform, FApp::GetProjectName(), ShaderCodeDir, RootMetaDataPath, PlatformSCLCSVPaths, ErrorString #if !UE_VERSION_OLDER_THAN(5,1,0) - ,bOutHasData + ,false #endif ); #else From 5d3fcf9185a8a3ddd9f5a8d79da665daf49e4eec Mon Sep 17 00:00:00 2001 From: hxhb Date: Thu, 1 Dec 2022 12:00:24 +0000 Subject: [PATCH 02/81] update submodule --- Mods/HotChunker | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mods/HotChunker b/Mods/HotChunker index ce47eff0..b4072669 160000 --- a/Mods/HotChunker +++ b/Mods/HotChunker @@ -1 +1 @@ -Subproject commit ce47eff048e94ec7d6a690bc6153d7ca8918f332 +Subproject commit b40726697f6bebf689134d3cacb80bbbdf874888 From f2cd1b4acec2a130aeab06f8baf21a73eb103e25 Mon Sep 17 00:00:00 2001 From: hxhb Date: Sat, 3 Dec 2022 09:41:22 +0000 Subject: [PATCH 03/81] optimize impl --- .../AssetTypeActions_PrimaryAssetLabel.cpp | 2 +- .../Private/HotPatcherEditor.cpp | 35 +++++++++++++------ .../Public/HotPatcherEditor.h | 4 ++- Mods/HotChunker | 2 +- 4 files changed, 29 insertions(+), 14 deletions(-) diff --git a/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/AssetActions/AssetTypeActions_PrimaryAssetLabel.cpp b/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/AssetActions/AssetTypeActions_PrimaryAssetLabel.cpp index 58758897..618441db 100644 --- a/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/AssetActions/AssetTypeActions_PrimaryAssetLabel.cpp +++ b/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/AssetActions/AssetTypeActions_PrimaryAssetLabel.cpp @@ -138,7 +138,7 @@ void FAssetTypeActions_PrimaryAssetLabel::MakeCookAndPakActionsSubMenu(UToolMenu FToolMenuSection& Section = Menu->AddSection("CookAndPakActionsSection"); UHotPatcherSettings* Settings = GetMutableDefault(); Settings->ReloadConfig(); - for (auto Platform : FHotPatcherEditorModule::Get().GetAllCookPlatforms()) + for (auto Platform : FHotPatcherEditorModule::Get().GetAllowCookPlatforms()) { if(Settings->bWhiteListCookInEditor && !Settings->PlatformWhitelists.Contains(Platform)) continue; diff --git a/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherEditor.cpp b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherEditor.cpp index 2024bc3b..f67f17bd 100644 --- a/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherEditor.cpp +++ b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherEditor.cpp @@ -330,13 +330,8 @@ void FHotPatcherEditorModule::ExtendContentBrowserPathSelectionMenu() void FHotPatcherEditorModule::MakeCookActionsSubMenu(UToolMenu* Menu) { FToolMenuSection& Section = Menu->AddSection("CookActionsSection"); - UHotPatcherSettings* Settings = GetMutableDefault(); - Settings->ReloadConfig(); - - for (auto Platform : GetAllCookPlatforms()) + for (auto Platform : GetAllowCookPlatforms()) { - if(Settings->bWhiteListCookInEditor && !Settings->PlatformWhitelists.Contains(Platform)) - continue; Section.AddMenuEntry( FName(*THotPatcherTemplateHelper::GetEnumNameByValue(Platform)), FText::Format(LOCTEXT("Platform", "{0}"), UKismetTextLibrary::Conv_StringToText(THotPatcherTemplateHelper::GetEnumNameByValue(Platform))), @@ -354,13 +349,9 @@ void FHotPatcherEditorModule::MakeCookAndPakActionsSubMenu(UToolMenu* Menu) FToolMenuSection& Section = Menu->AddSection("CookAndPakActionsSection"); UHotPatcherSettings* Settings = GetMutableDefault(); Settings->ReloadConfig(); - for (ETargetPlatform Platform : GetAllCookPlatforms()) + for (ETargetPlatform Platform : GetAllowCookPlatforms()) { FString PlatformName = THotPatcherTemplateHelper::GetEnumNameByValue(Platform); - if(PlatformName.StartsWith(TEXT("All"))) - continue; - if(Settings->bWhiteListCookInEditor && !Settings->PlatformWhitelists.Contains(Platform)) - continue; FToolMenuEntry& PlatformEntry = Section.AddSubMenu(FName(*PlatformName), FText::Format(LOCTEXT("Platform", "{0}"), UKismetTextLibrary::Conv_StringToText(THotPatcherTemplateHelper::GetEnumNameByValue(Platform))), FText(), @@ -695,6 +686,28 @@ TArray FHotPatcherEditorModule::GetAllCookPlatforms() const return TargetPlatforms; } +TArray FHotPatcherEditorModule::GetAllowCookPlatforms() const +{ + TArray results; + UHotPatcherSettings* Settings = GetMutableDefault(); + Settings->ReloadConfig(); + for (auto Platform : GetAllCookPlatforms()) + { + if(Settings->bWhiteListCookInEditor && !Settings->PlatformWhitelists.Contains(Platform)) + continue; + FString PlatformName = THotPatcherTemplateHelper::GetEnumNameByValue(Platform); + if(PlatformName.StartsWith(TEXT("All"))) + continue; + results.AddUnique(Platform); + } + return results; +} + +void FHotPatcherEditorModule::OnCookPlatformForExterner(ETargetPlatform Platform) +{ + FHotPatcherEditorModule::Get().OnCookPlatform(Platform); +} + TSharedPtr FHotPatcherEditorModule::RunProcMission(const FString& Bin, const FString& Command, const FString& MissionName) { if (mProcWorkingThread.IsValid() && mProcWorkingThread->GetThreadStatus()==EThreadStatus::Busy) diff --git a/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherEditor.h b/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherEditor.h index ae446b33..e335d8e0 100644 --- a/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherEditor.h +++ b/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherEditor.h @@ -70,7 +70,8 @@ class HOTPATCHEREDITOR_API FHotPatcherEditorModule : public IModuleInterface void MakeHotPatcherPresetsActionsSubMenu(UToolMenu* Menu); void OnAddToPatchSettings(const FToolMenuContext& MenuContent); #endif - TArray GetAllCookPlatforms()const; + TArray GetAllowCookPlatforms() const; + static void OnCookPlatformForExterner(ETargetPlatform Platform);; void OnCookPlatform(ETargetPlatform Platform); void OnCookAndPakPlatform(ETargetPlatform Platform, bool bAnalysicDependencies); void CookAndPakByAssetsAndFilters(TArray IncludeAssets,TArray IncludePaths,TArray Platforms,bool bForceStandalone = false); @@ -89,6 +90,7 @@ class HOTPATCHEREDITOR_API FHotPatcherEditorModule : public IModuleInterface bool bCook ); private: + TArray GetAllCookPlatforms()const; TSharedRef OnSpawnPluginTab(const class FSpawnTabArgs& InSpawnTabArgs); void OnTabClosed(TSharedRef InTab); TArray GetSelectedAssetsInBrowserContent(); diff --git a/Mods/HotChunker b/Mods/HotChunker index b4072669..b6b02dc5 160000 --- a/Mods/HotChunker +++ b/Mods/HotChunker @@ -1 +1 @@ -Subproject commit b40726697f6bebf689134d3cacb80bbbdf874888 +Subproject commit b6b02dc5956ea58d0d2581da76fe4419b667af21 From 948cddbe68c7a36c7957ce64f6b7567c3e213ab0 Mon Sep 17 00:00:00 2001 From: lipengzha Date: Sun, 4 Dec 2022 17:40:27 +0800 Subject: [PATCH 04/81] support 5.1 --- .../Private/ShaderPatch/FlibShaderCodeLibraryHelper.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/FlibShaderCodeLibraryHelper.cpp b/HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/FlibShaderCodeLibraryHelper.cpp index 41872f34..a2ca0ca9 100644 --- a/HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/FlibShaderCodeLibraryHelper.cpp +++ b/HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/FlibShaderCodeLibraryHelper.cpp @@ -120,10 +120,12 @@ bool UFlibShaderCodeLibraryHelper::SaveShaderLibrary(const ITargetPlatform* Targ #if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 25 #if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 26 FString ErrorString; - +#if !UE_VERSION_OLDER_THAN(5,1,0) + bool bOutHasData = false; +#endif bSaved = SHADER_COOKER_CLASS::SaveShaderLibraryWithoutChunking(TargetPlatform, FApp::GetProjectName(), ShaderCodeDir, RootMetaDataPath, PlatformSCLCSVPaths, ErrorString #if !UE_VERSION_OLDER_THAN(5,1,0) - ,false + ,bOutHasData #endif ); #else From 474a63b405f1ff1be91d83b5f889de6641f8109c Mon Sep 17 00:00:00 2001 From: lipengzha Date: Sun, 4 Dec 2022 20:00:26 +0800 Subject: [PATCH 05/81] fix 5.1 WP error --- .../PackageWriter/HotWorldPartitionCookPackageSplitter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HotPatcher/Source/HotPatcherCore/Private/Cooker/PackageWriter/HotWorldPartitionCookPackageSplitter.cpp b/HotPatcher/Source/HotPatcherCore/Private/Cooker/PackageWriter/HotWorldPartitionCookPackageSplitter.cpp index caae8b03..d3ad5c78 100644 --- a/HotPatcher/Source/HotPatcherCore/Private/Cooker/PackageWriter/HotWorldPartitionCookPackageSplitter.cpp +++ b/HotPatcher/Source/HotPatcherCore/Private/Cooker/PackageWriter/HotWorldPartitionCookPackageSplitter.cpp @@ -15,7 +15,7 @@ #include "Editor.h" // Register FHotWorldPartitionCookPackageSplitter for UWorld class -REGISTER_COOKPACKAGE_SPLITTER(FHotWorldPartitionCookPackageSplitter, UWorld); +// REGISTER_COOKPACKAGE_SPLITTER(FHotWorldPartitionCookPackageSplitter, UWorld); bool FHotWorldPartitionCookPackageSplitter::ShouldSplit(UObject* SplitData) { From 317340e6905641ef2e2f118866699170de8720ef Mon Sep 17 00:00:00 2001 From: hxhb Date: Wed, 7 Dec 2022 11:22:35 +0000 Subject: [PATCH 06/81] optimize impl --- .../Commandlets/HotPatcherCommandlet.cpp | 2 +- .../Cooker/MultiCooker/SingleCookerProxy.cpp | 97 +++---- .../Private/CreatePatch/PatcherProxy.cpp | 3 + .../Private/FlibHotPatcherCoreHelper.cpp | 45 +++ .../FlibShaderCodeLibraryHelper.cpp | 18 +- .../Public/CommandletBase/CommandletHelper.h | 4 +- .../Public/FlibHotPatcherCoreHelper.h | 5 +- .../ShaderPatch/FlibShaderCodeLibraryHelper.h | 3 +- .../Private/HotPatcherEditor.cpp | 24 +- .../Public/HotPatcherEditor.h | 2 +- .../Private/BaseTypes/FChunkInfo.cpp | 1 + .../CreatePatch/FExportPatchSettings.cpp | 9 + .../CreatePatch/HotPatcherSettingBase.cpp | 15 + .../FDefaultAssetDependenciesParser.cpp | 2 +- .../Private/FlibAssetManageHelper.cpp | 22 ++ .../Private/FlibPatchParserHelper.cpp | 262 +++++++++++------- .../Public/BaseTypes/FPackageTracker.h | 33 +++ .../Public/CreatePatch/FExportPatchSettings.h | 2 + .../CreatePatch/HotPatcherSettingBase.h | 10 +- .../Public/FlibAssetManageHelper.h | 6 +- .../Public/FlibPatchParserHelper.h | 4 +- Mods/HotChunker | 2 +- Mods/HotMultiCooker | 2 +- 23 files changed, 396 insertions(+), 177 deletions(-) diff --git a/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotPatcherCommandlet.cpp b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotPatcherCommandlet.cpp index 98be16b0..df4bb57f 100644 --- a/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotPatcherCommandlet.cpp +++ b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotPatcherCommandlet.cpp @@ -68,7 +68,7 @@ int32 UHotPatcherCommandlet::Main(const FString& Params) FString FinalConfig; THotPatcherTemplateHelper::TSerializeStructAsJsonString(*ExportPatchSetting,FinalConfig); - UE_LOG(LogHotPatcherCommandlet, Display, TEXT("%s"), *FinalConfig); + // UE_LOG(LogHotPatcherCommandlet, Display, TEXT("%s"), *FinalConfig); UPatcherProxy* PatcherProxy = NewObject(); diff --git a/HotPatcher/Source/HotPatcherCore/Private/Cooker/MultiCooker/SingleCookerProxy.cpp b/HotPatcher/Source/HotPatcherCore/Private/Cooker/MultiCooker/SingleCookerProxy.cpp index 31a3af14..e741717c 100644 --- a/HotPatcher/Source/HotPatcherCore/Private/Cooker/MultiCooker/SingleCookerProxy.cpp +++ b/HotPatcher/Source/HotPatcherCore/Private/Cooker/MultiCooker/SingleCookerProxy.cpp @@ -56,7 +56,8 @@ void USingleCookerProxy::Init(FPatcherEntitySettingBase* InSetting) Super::Init(InSetting); // IConsoleVariable* StreamableDelegateDelayFrames = IConsoleManager::Get().FindConsoleVariable(TEXT("s.StreamableDelegateDelayFrames")); // StreamableDelegateDelayFrames->Set(0); - + UFlibHotPatcherCoreHelper::DumpActiveTargetPlatforms(); + #if WITH_PACKAGE_CONTEXT if(GetSettingObject()->bOverrideSavePackageContext) { @@ -112,62 +113,52 @@ int32 USingleCookerProxy::MakeCookQueue(FCookCluster& InCluser) const int32 NumberOfAssetsPerFrame = GetSettingObject()->GetNumberOfAssetsPerFrame(); - if(GetSettingObject()->bForceCookInOneFrame) + for(auto Class:GetPreCacheClasses()) { - CookCluserQueue.Enqueue(InCluser); - ++MakeClusterCount; - return MakeClusterCount; - } - else - { - for(auto Class:GetPreCacheClasses()) + TArray ObjectAssets = UFlibAssetManageHelper::GetAssetDetailsByClass(InCluser.AssetDetails,Class,true); + if(ObjectAssets.Num()) { - TArray ObjectAssets = UFlibAssetManageHelper::GetAssetDetailsByClass(InCluser.AssetDetails,Class,true); - if(ObjectAssets.Num()) - { - DumpCookerInfo.Append(FString::Printf(TEXT("\t%s -- %d\n"),*Class->GetName(),ObjectAssets.Num())); - } - int32 ClassesNumberOfAssetsPerFrame = GetClassAssetNumOfPerCluster(Class); + DumpCookerInfo.Append(FString::Printf(TEXT("\t%s -- %d\n"),*Class->GetName(),ObjectAssets.Num())); + } + int32 ClassesNumberOfAssetsPerFrame = GetClassAssetNumOfPerCluster(Class); - while(ObjectAssets.Num()) - { - int32 ClusterAssetNum = ClassesNumberOfAssetsPerFrame < 1 ? ObjectAssets.Num() : ClassesNumberOfAssetsPerFrame; - int32 NewClusterAssetNum = FMath::Min(ClusterAssetNum,ObjectAssets.Num()); + while(ObjectAssets.Num()) + { + int32 ClusterAssetNum = ClassesNumberOfAssetsPerFrame < 1 ? ObjectAssets.Num() : ClassesNumberOfAssetsPerFrame; + int32 NewClusterAssetNum = FMath::Min(ClusterAssetNum,ObjectAssets.Num()); - TArray CulsterObjectAssets(ObjectAssets.GetData(),NewClusterAssetNum); - FCookCluster NewCluster; - NewCluster.AssetDetails = std::move(CulsterObjectAssets); - ObjectAssets.RemoveAt(0,NewClusterAssetNum); - NewCluster.Platforms = GetSettingObject()->CookTargetPlatforms; - NewCluster.bPreGeneratePlatformData = GetSettingObject()->bPreGeneratePlatformData; - - NewCluster.CookActionCallback.OnAssetCooked = GetOnPackageSavedCallback(); - NewCluster.CookActionCallback.OnCookBegin = GetOnCookAssetBeginCallback(); - CookCluserQueue.Enqueue(NewCluster); - ++MakeClusterCount; - } + TArray CulsterObjectAssets(ObjectAssets.GetData(),NewClusterAssetNum); + FCookCluster NewCluster; + NewCluster.AssetDetails = std::move(CulsterObjectAssets); + ObjectAssets.RemoveAt(0,NewClusterAssetNum); + NewCluster.Platforms = GetSettingObject()->CookTargetPlatforms; + NewCluster.bPreGeneratePlatformData = GetSettingObject()->bPreGeneratePlatformData; + NewCluster.CookActionCallback.OnAssetCooked = GetOnPackageSavedCallback(); + NewCluster.CookActionCallback.OnCookBegin = GetOnCookAssetBeginCallback(); + CookCluserQueue.Enqueue(NewCluster); + ++MakeClusterCount; } - - if(InCluser.AssetDetails.Num()) + } + if(InCluser.AssetDetails.Num()) + { + int32 OtherAssetNumPerFrame = NumberOfAssetsPerFrame < 1 ? InCluser.AssetDetails.Num() : NumberOfAssetsPerFrame; + int32 SplitNum = (InCluser.AssetDetails.Num() / OtherAssetNumPerFrame) + 1; + + const TArray> SplitedAssets= THotPatcherTemplateHelper::SplitArray(InCluser.AssetDetails,SplitNum); + for(const auto& AssetDetails:SplitedAssets) { - int32 OtherAssetNumPerFrame = NumberOfAssetsPerFrame < 1 ? InCluser.AssetDetails.Num() : NumberOfAssetsPerFrame; - int32 SplitNum = (InCluser.AssetDetails.Num() / OtherAssetNumPerFrame) + 1; - - const TArray> SplitedAssets= THotPatcherTemplateHelper::SplitArray(InCluser.AssetDetails,SplitNum); - for(const auto& AssetDetails:SplitedAssets) - { - FCookCluster NewCluster; - NewCluster.AssetDetails = std::move(AssetDetails); - NewCluster.Platforms = GetSettingObject()->CookTargetPlatforms; - NewCluster.bPreGeneratePlatformData = GetSettingObject()->bPreGeneratePlatformData; - NewCluster.CookActionCallback.OnAssetCooked = GetOnPackageSavedCallback(); - NewCluster.CookActionCallback.OnCookBegin = GetOnCookAssetBeginCallback(); - CookCluserQueue.Enqueue(NewCluster); - ++MakeClusterCount; - } - DumpCookerInfo.Append(FString::Printf(TEXT("\tOther Assets -- %d, make %d cluster.\n"),InCluser.AssetDetails.Num(),SplitedAssets.Num())); + FCookCluster NewCluster; + NewCluster.AssetDetails = std::move(AssetDetails); + NewCluster.Platforms = GetSettingObject()->CookTargetPlatforms; + NewCluster.bPreGeneratePlatformData = GetSettingObject()->bPreGeneratePlatformData; + NewCluster.CookActionCallback.OnAssetCooked = GetOnPackageSavedCallback(); + NewCluster.CookActionCallback.OnCookBegin = GetOnCookAssetBeginCallback(); + CookCluserQueue.Enqueue(NewCluster); + ++MakeClusterCount; } + DumpCookerInfo.Append(FString::Printf(TEXT("\tOther Assets -- %d, make %d cluster.\n"),InCluser.AssetDetails.Num(),SplitedAssets.Num())); } + DumpCookerInfo.Append(TEXT("---------------------------------------------------------------------\n")); UE_LOG(LogHotPatcher,Display,TEXT("%s"),*DumpCookerInfo); return MakeClusterCount; @@ -294,8 +285,8 @@ void USingleCookerProxy::ExecCookCluster(const FCookCluster& CookCluster) return; } + bool bContainShaderCluster = UFlibHotPatcherCoreHelper::AssetDetailsHasClasses(CookCluster.AssetDetails,UFlibHotPatcherCoreHelper::GetAllMaterialClassesNames()); DumpCluster(CookCluster,GCookLog); - TArray TargetPlatforms = UFlibHotPatcherCoreHelper::GetTargetPlatformsByNames(CookCluster.Platforms); FString CookBaseDir = GetSettingObject()->GetStorageCookedAbsDir(); CleanOldCooked(CookBaseDir,CookCluster.AsSoftObjectPaths(),CookCluster.Platforms); @@ -303,6 +294,8 @@ void USingleCookerProxy::ExecCookCluster(const FCookCluster& CookCluster) #if WITH_PACKAGE_CONTEXT TMap SavePackageContextsNameMapping = GetPlatformSavePackageContextsNameMapping(); #endif + + TSharedPtr ClassesPackageTracker = MakeShareable(new FClassesPackageTracker); TArray PreCachePackages = UFlibAssetManageHelper::LoadPackagesForCooking(CookCluster.AsSoftObjectPaths(),GetSettingObject()->bConcurrentSave); bool bCanConcurrentSave = GetSettingObject()->bConcurrentSave && CookCluster.bPreGeneratePlatformData; @@ -371,7 +364,6 @@ void USingleCookerProxy::ExecCookCluster(const FCookCluster& CookCluster) // clean cached ddd / release memory // CleanClusterCachedPlatformData(CookCluster); - UFlibShaderCodeLibraryHelper::WaitShaderCompilingComplete(); UFlibHotPatcherCoreHelper::WaitForAsyncFileWrites(); // for GC @@ -607,6 +599,8 @@ FCookCluster USingleCookerProxy::GetPackageTrackerAsCluster() { // make asset data to asset registry FSoftObjectPath ObjectPath(PackagePath.ToString()); + UFlibAssetManageHelper::UpdateAssetRegistryData(ObjectPath.GetLongPackageName()); + FAssetData AssetData; if(UAssetManager::Get().GetAssetDataForPath(ObjectPath,AssetData)) { @@ -724,7 +718,6 @@ void USingleCookerProxy::CleanOldCooked(const FString& CookBaseDir,const TArray< } } - bool USingleCookerProxy::HasError() { SCOPED_NAMED_EVENT_TEXT("USingleCookerProxy::HasError",FColor::Red); diff --git a/HotPatcher/Source/HotPatcherCore/Private/CreatePatch/PatcherProxy.cpp b/HotPatcher/Source/HotPatcherCore/Private/CreatePatch/PatcherProxy.cpp index 8ecab750..9eb01bec 100644 --- a/HotPatcher/Source/HotPatcherCore/Private/CreatePatch/PatcherProxy.cpp +++ b/HotPatcher/Source/HotPatcherCore/Private/CreatePatch/PatcherProxy.cpp @@ -158,6 +158,7 @@ namespace PatchWorker void UPatcherProxy::Init(FPatcherEntitySettingBase* InSetting) { + SCOPED_NAMED_EVENT_TEXT("UPatcherProxy::Init",FColor::Red); Super::Init(InSetting); #if WITH_PACKAGE_CONTEXT PlatformSavePackageContexts = UFlibHotPatcherCoreHelper::CreatePlatformsPackageContexts( @@ -200,6 +201,7 @@ void UPatcherProxy::Shutdown() bool UPatcherProxy::DoExport() { + SCOPED_NAMED_EVENT_TEXT("UPatcherProxy::DoExport",FColor::Red); PatchContext = MakeShareable(new FHotPatcherPatchContext); PatchContext->PatchProxy = this; PatchContext->OnPaking.AddLambda([this](const FString& One,const FString& Msg){this->OnPaking.Broadcast(One,Msg);}); @@ -584,6 +586,7 @@ namespace PatchWorker EmptySetting.bConcurrentSave = false; // for current impl arch EmptySetting.bForceCookInOneFrame = true; + EmptySetting.NumberOfAssetsPerFrame = 200; EmptySetting.bDisplayConfig = false; EmptySetting.StorageCookedDir = Context.GetSettingObject()->GetStorageCookedDir();//FPaths::Combine(FPaths::ConvertRelativePathToFull(FPaths::ProjectSavedDir()),TEXT("Cooked")); EmptySetting.StorageMetadataDir = FPaths::Combine(Context.GetSettingObject()->GetSaveAbsPath(),Context.CurrentVersion.VersionId,TEXT("Metadatas"),Chunk.ChunkName); diff --git a/HotPatcher/Source/HotPatcherCore/Private/FlibHotPatcherCoreHelper.cpp b/HotPatcher/Source/HotPatcherCore/Private/FlibHotPatcherCoreHelper.cpp index 3a4cf9ca..c8f63764 100644 --- a/HotPatcher/Source/HotPatcherCore/Private/FlibHotPatcherCoreHelper.cpp +++ b/HotPatcher/Source/HotPatcherCore/Private/FlibHotPatcherCoreHelper.cpp @@ -2554,8 +2554,10 @@ TempSandboxFile.Get() #endif return bresult; } + TArray UFlibHotPatcherCoreHelper::GetClassesByNames(const TArray& ClassesNames) { + SCOPED_NAMED_EVENT_TEXT("GetClassesByNames",FColor::Red); TArray result; for(const auto& ClassesName:ClassesNames) { @@ -2573,6 +2575,7 @@ TArray UFlibHotPatcherCoreHelper::GetClassesByNames(const TArray TArray UFlibHotPatcherCoreHelper::GetAllMaterialClasses() { + SCOPED_NAMED_EVENT_TEXT("GetAllMaterialClasses",FColor::Red); TArray Classes; TArray ParentClassesName = { // materials @@ -2589,8 +2592,34 @@ TArray UFlibHotPatcherCoreHelper::GetAllMaterialClasses() return Classes; } +bool UFlibHotPatcherCoreHelper::IsMaterialClasses(UClass* Class) +{ + return UFlibHotPatcherCoreHelper::IsMaterialClassName(Class->GetFName()); +}; + +bool UFlibHotPatcherCoreHelper::IsMaterialClassName(FName ClassName) +{ + return UFlibHotPatcherCoreHelper::GetAllMaterialClassesNames().Contains(ClassName); +} + +bool UFlibHotPatcherCoreHelper::AssetDetailsHasClasses(const TArray& AssetDetails, TSet ClasssName) +{ + SCOPED_NAMED_EVENT_TEXT("AssetDetailsHasClasses",FColor::Red); + bool bHas = false; + for(const auto& Detail:AssetDetails) + { + if(ClasssName.Contains(Detail.AssetType)) + { + bHas = true; + break; + } + } + return bHas; +} + TSet UFlibHotPatcherCoreHelper::GetAllMaterialClassesNames() { + SCOPED_NAMED_EVENT_TEXT("GetAllMaterialClassesNames",FColor::Red); TSet result; for(const auto& Class:GetAllMaterialClasses()) { @@ -2654,3 +2683,19 @@ TArray UFlibHotPatcherCoreHelper::GetPreCacheClasses() } return Results.Array(); } + +void UFlibHotPatcherCoreHelper::DumpActiveTargetPlatforms() +{ + FString ActiveTargetPlatforms; + ITargetPlatformManagerModule* TPM = GetTargetPlatformManager(); + if (TPM && (TPM->RestrictFormatsToRuntimeOnly() == false)) + { + TArray Platforms = TPM->GetActiveTargetPlatforms(); + for(auto& Platform:Platforms) + { + ActiveTargetPlatforms += FString::Printf(TEXT("%s,"),*Platform->PlatformName()); + } + ActiveTargetPlatforms.RemoveFromEnd(TEXT(",")); + } + UE_LOG(LogHotPatcherCoreHelper,Display,TEXT("[IMPORTTENT] ActiveTargetPlatforms: %s"),*ActiveTargetPlatforms); +} \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/FlibShaderCodeLibraryHelper.cpp b/HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/FlibShaderCodeLibraryHelper.cpp index a2ca0ca9..f1136024 100644 --- a/HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/FlibShaderCodeLibraryHelper.cpp +++ b/HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/FlibShaderCodeLibraryHelper.cpp @@ -246,7 +246,6 @@ void UFlibShaderCodeLibraryHelper::WaitShaderCompilingComplete() if (GShaderCompilingManager) { SCOPED_NAMED_EVENT_TEXT("WaitShaderCompileComplete",FColor::Red); - int32 LastRemainingJob = 0; while(GShaderCompilingManager->IsCompiling()) { GShaderCompilingManager->ProcessAsyncResults(false, false); @@ -256,12 +255,14 @@ void UFlibShaderCodeLibraryHelper::WaitShaderCompilingComplete() // UE_LOG(LogHotPatcher,Display,TEXT("Remaining Shader %d"),CurrentNumRemaingingJobs); // LastRemainingJob = CurrentNumRemaingingJobs; // } + // GShaderCompilingManager->FinishAllCompilation(); FPlatformProcess::Sleep(0.5f); GLog->Flush(); } // One last process to get the shaders that were compiled at the very end GShaderCompilingManager->ProcessAsyncResults(false, false); + GShaderCompilingManager->FinishAllCompilation(); } } @@ -272,3 +273,18 @@ void UFlibShaderCodeLibraryHelper::CleanShaderWorkerDir() UE_LOG(LogHotPatcher, Warning, TEXT("Could not delete the shader compiler working directory '%s'."), *FPaths::ShaderWorkingDir()); } } + +void UFlibShaderCodeLibraryHelper::CancelMaterialShaderCompile(UMaterialInterface* MaterialInterface) +{ + if(MaterialInterface) + { + UMaterial* Material = MaterialInterface->GetMaterial(); + for (int32 FeatureLevel = 0; FeatureLevel < ERHIFeatureLevel::Num; ++FeatureLevel) + { + if (FMaterialResource* Res = Material->GetMaterialResource((ERHIFeatureLevel::Type)FeatureLevel)) + { + Res->CancelCompilation(); + } + } + } +} diff --git a/HotPatcher/Source/HotPatcherCore/Public/CommandletBase/CommandletHelper.h b/HotPatcher/Source/HotPatcherCore/Public/CommandletBase/CommandletHelper.h index 54cec00b..64849b37 100644 --- a/HotPatcher/Source/HotPatcherCore/Public/CommandletBase/CommandletHelper.h +++ b/HotPatcher/Source/HotPatcherCore/Public/CommandletBase/CommandletHelper.h @@ -1,9 +1,7 @@ #pragma once +#include "CoreMinimal.h" #include "ETargetPlatform.h" -#include "FlibPatchParserHelper.h" -#include "ThreadUtils/FProcWorkerThread.hpp" -#include "HotPatcherLog.h" #define PATCHER_CONFIG_PARAM_NAME TEXT("-config=") #define ADD_PATCH_PLATFORMS TEXT("AddPatchPlatforms") diff --git a/HotPatcher/Source/HotPatcherCore/Public/FlibHotPatcherCoreHelper.h b/HotPatcher/Source/HotPatcherCore/Public/FlibHotPatcherCoreHelper.h index c57dcd28..1777d82d 100644 --- a/HotPatcher/Source/HotPatcherCore/Public/FlibHotPatcherCoreHelper.h +++ b/HotPatcher/Source/HotPatcherCore/Public/FlibHotPatcherCoreHelper.h @@ -285,7 +285,10 @@ class HOTPATCHERCORE_API UFlibHotPatcherCoreHelper : public UBlueprintFunctionLi static bool SerializeChunksManifests(ITargetPlatform* TargetPlatform, const TSet&, const TSet&, bool bGenerateStreamingInstallManifest = true); static TArray GetClassesByNames(const TArray& ClassesNames); static TArray GetAllMaterialClasses(); + static bool IsMaterialClasses(UClass* Class); + static bool IsMaterialClassName(FName ClassName); + static bool AssetDetailsHasClasses(const TArray& AssetDetails,TSet ClasssName); static TSet GetAllMaterialClassesNames(); static TArray GetPreCacheClasses(); - + static void DumpActiveTargetPlatforms(); }; diff --git a/HotPatcher/Source/HotPatcherCore/Public/ShaderPatch/FlibShaderCodeLibraryHelper.h b/HotPatcher/Source/HotPatcherCore/Public/ShaderPatch/FlibShaderCodeLibraryHelper.h index fd2fd6ef..54345cc8 100644 --- a/HotPatcher/Source/HotPatcherCore/Public/ShaderPatch/FlibShaderCodeLibraryHelper.h +++ b/HotPatcher/Source/HotPatcherCore/Public/ShaderPatch/FlibShaderCodeLibraryHelper.h @@ -58,6 +58,7 @@ class HOTPATCHERCORE_API UFlibShaderCodeLibraryHelper : public UBlueprintFunctio static TArray FindCookedShaderLibByShaderFrmat(const FString& ShaderFormatName,const FString& Directory); static void WaitShaderCompilingComplete(); - + static void CancelMaterialShaderCompile(UMaterialInterface* MaterialInterface); + static void CleanShaderWorkerDir(); }; diff --git a/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherEditor.cpp b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherEditor.cpp index f67f17bd..fe61a80f 100644 --- a/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherEditor.cpp +++ b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherEditor.cpp @@ -568,6 +568,17 @@ void FHotPatcherEditorModule::CookAndPakByAssetsAndFilters(TArray{},Platforms,true); CookAndPakByPatchSettings(PatchSettings,bForceStandalone); } +FString GetPlatformsStr(TArray Platforms) +{ + FString result; + for(auto Platform:Platforms) + { + FString PlatformStr = THotPatcherTemplateHelper::GetEnumNameByValue(Platform,false); + result+=FString::Printf(TEXT("%s,"),*PlatformStr); + } + result.RemoveFromEnd(TEXT(",")); + return result; +} void FHotPatcherEditorModule::CookAndPakByPatchSettings(TSharedPtr InPatchSettings,bool bForceStandalone) { @@ -579,7 +590,9 @@ void FHotPatcherEditorModule::CookAndPakByPatchSettings(TSharedPtrGetCombinedAdditionalCommandletArgs()); UE_LOG(LogHotPatcher,Log,TEXT("HotPatcher %s Mission: %s %s"),*InPatchSettings->VersionId,*UFlibHotPatcherCoreHelper::GetUECmdBinary(),*MissionCommand); - RunProcMission(UFlibHotPatcherCoreHelper::GetUECmdBinary(),MissionCommand,FString::Printf(TEXT("Mission: %s"),*InPatchSettings->VersionId)); + + FText DisplayText = UKismetTextLibrary::Conv_StringToText(FString::Printf(TEXT("Packaging %s for %s..."),*InPatchSettings->VersionId,*GetPlatformsStr(InPatchSettings->PakTargetPlatforms))); + RunProcMission(UFlibHotPatcherCoreHelper::GetUECmdBinary(),MissionCommand,InPatchSettings->VersionId,DisplayText); } else { @@ -708,7 +721,7 @@ void FHotPatcherEditorModule::OnCookPlatformForExterner(ETargetPlatform Platform FHotPatcherEditorModule::Get().OnCookPlatform(Platform); } -TSharedPtr FHotPatcherEditorModule::RunProcMission(const FString& Bin, const FString& Command, const FString& MissionName) +TSharedPtr FHotPatcherEditorModule::RunProcMission(const FString& Bin, const FString& Command, const FString& MissionName, const FText& NotifyTextOverride) { if (mProcWorkingThread.IsValid() && mProcWorkingThread->GetThreadStatus()==EThreadStatus::Busy) { @@ -722,8 +735,13 @@ TSharedPtr FHotPatcherEditorModule::RunProcMission(const FStr mProcWorkingThread->ProcSuccessedDelegate.AddUObject(MissionNotifyProay,&UMissionNotificationProxy::SpawnMissionSuccessedNotification); mProcWorkingThread->ProcFaildDelegate.AddUObject(MissionNotifyProay,&UMissionNotificationProxy::SpawnMissionFaildNotification); MissionNotifyProay->SetMissionName(*FString::Printf(TEXT("%s"),*MissionName)); + FText DisplayText = NotifyTextOverride; + if(DisplayText.IsEmpty()) + { + DisplayText = FText::FromString(FString::Printf(TEXT("%s in progress"),*MissionName)); + } MissionNotifyProay->SetMissionNotifyText( - FText::FromString(FString::Printf(TEXT("%s in progress"),*MissionName)), + DisplayText, LOCTEXT("RunningCookNotificationCancelButton", "Cancel"), FText::FromString(FString::Printf(TEXT("%s Mission Finished!"),*MissionName)), FText::FromString(FString::Printf(TEXT("%s Failed!"),*MissionName)) diff --git a/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherEditor.h b/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherEditor.h index e335d8e0..ffee617d 100644 --- a/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherEditor.h +++ b/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherEditor.h @@ -56,7 +56,7 @@ class HOTPATCHEREDITOR_API FHotPatcherEditorModule : public IModuleInterface TSharedPtr CreateProcMissionThread(const FString& Bin, const FString& Command, const FString& MissionName); - TSharedPtr RunProcMission(const FString& Bin, const FString& Command, const FString& MissionName); + TSharedPtr RunProcMission(const FString& Bin, const FString& Command, const FString& MissionName, const FText& NotifyTextOverride = FText{}); #if WITH_EDITOR_SECTION diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/BaseTypes/FChunkInfo.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/BaseTypes/FChunkInfo.cpp index 5a2646a4..ddb91085 100644 --- a/HotPatcher/Source/HotPatcherRuntime/Private/BaseTypes/FChunkInfo.cpp +++ b/HotPatcher/Source/HotPatcherRuntime/Private/BaseTypes/FChunkInfo.cpp @@ -31,6 +31,7 @@ FString FChunkInfo::GetShaderLibraryName() const TArray FChunkInfo::GetManagedAssets() const { + SCOPED_NAMED_EVENT_TEXT("FChunkInfo::GetManagedAssets",FColor::Red); TArray NewPaths; UAssetManager& Manager = UAssetManager::Get(); diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/FExportPatchSettings.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/FExportPatchSettings.cpp index c84169f7..872194ec 100644 --- a/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/FExportPatchSettings.cpp +++ b/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/FExportPatchSettings.cpp @@ -127,6 +127,15 @@ FString FExportPatchSettings::GetCurrentVersionSavePath() const return CurrentVersionSavePath; } +FString FExportPatchSettings::GetCombinedAdditionalCommandletArgs() const +{ + FString Result = FString::Printf(TEXT("%s %s"), + *FHotPatcherSettingBase::GetCombinedAdditionalCommandletArgs(), + *UFlibPatchParserHelper::GetTargetPlatformsCmdLine(GetPakTargetPlatforms()) + ); + return Result; +} + FString FExportPatchSettings::GetStorageCookedDir() const { return UFlibPatchParserHelper::ReplaceMark(StorageCookedDir); diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/HotPatcherSettingBase.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/HotPatcherSettingBase.cpp index 05f8479c..4bd5ae83 100644 --- a/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/HotPatcherSettingBase.cpp +++ b/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/HotPatcherSettingBase.cpp @@ -102,6 +102,21 @@ FString FHotPatcherSettingBase::GetSaveAbsPath()const } +FString FHotPatcherSettingBase::GetCombinedAdditionalCommandletArgs() const +{ + FString Result; + + // for(const auto& Option:GetAdditionalCommandletArgs()) + // { + // Result+=FString::Printf(TEXT("%s "),*Option); + // } + + TArray Options = GetAdditionalCommandletArgs(); + Options.AddUnique(TEXT("-NoPostLoadCacheDDC")); + Result = UFlibPatchParserHelper::MergeOptionsAsCmdline(Options); + return Result; +} + TArray FHotPatcherSettingBase::GetAllSkipContents() const { diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/DependenciesParser/FDefaultAssetDependenciesParser.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/DependenciesParser/FDefaultAssetDependenciesParser.cpp index a924b9aa..cb898cce 100644 --- a/HotPatcher/Source/HotPatcherRuntime/Private/DependenciesParser/FDefaultAssetDependenciesParser.cpp +++ b/HotPatcher/Source/HotPatcherRuntime/Private/DependenciesParser/FDefaultAssetDependenciesParser.cpp @@ -65,7 +65,7 @@ void FAssetDependenciesParser::Parse(const FAssetDependencies& InParseConfig) continue; } FAssetData AssetData; - if(UFlibAssetManageHelper::GetAssetsDataByPackageName(LongPackageName,AssetData)) + if(!LongPackageName.IsEmpty() && UFlibAssetManageHelper::GetAssetsDataByPackageName(LongPackageName,AssetData)) { if(IsIgnoreAsset(AssetData)) { diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/FlibAssetManageHelper.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/FlibAssetManageHelper.cpp index b0fcc5f2..2a800352 100644 --- a/HotPatcher/Source/HotPatcherRuntime/Private/FlibAssetManageHelper.cpp +++ b/HotPatcher/Source/HotPatcherRuntime/Private/FlibAssetManageHelper.cpp @@ -135,6 +135,18 @@ bool UFlibAssetManageHelper::GetAssetPackageGUID(const FString& InPackageName, F return bResult; } +FSoftObjectPath UFlibAssetManageHelper::CreateSoftObjectPathByPackage(UPackage* Package) +{ + FString AssetPathName = Package->GetPathName(); + FSoftObjectPath Path(UFlibAssetManageHelper::LongPackageNameToPackagePath(AssetPathName)); + return Path; +} + +FName UFlibAssetManageHelper::GetAssetTypeByPackage(UPackage* Package) +{ + return UFlibAssetManageHelper::GetAssetType(CreateSoftObjectPathByPackage(Package)); +} + FAssetDependenciesInfo UFlibAssetManageHelper::CombineAssetDependencies(const FAssetDependenciesInfo& A, const FAssetDependenciesInfo& B) { @@ -1585,4 +1597,14 @@ FName UFlibAssetManageHelper::GetObjectPathByAssetData(const FAssetData& Data) return NAME_None; } +void UFlibAssetManageHelper::UpdateAssetRegistryData(const FString& PackageName) +{ + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); + FString PackageFilename; + if (FPackageName::FindPackageFileWithoutExtension(FPackageName::LongPackageNameToFilename(PackageName), PackageFilename)) + { + AssetRegistry.ScanModifiedAssetFiles(TArray{PackageFilename}); + } +} // PRAGMA_ENABLE_DEPRECATION_WARNINGS diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/FlibPatchParserHelper.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/FlibPatchParserHelper.cpp index e1c24840..0365d489 100644 --- a/HotPatcher/Source/HotPatcherRuntime/Private/FlibPatchParserHelper.cpp +++ b/HotPatcher/Source/HotPatcherRuntime/Private/FlibPatchParserHelper.cpp @@ -927,113 +927,117 @@ FChunkAssetDescribe UFlibPatchParserHelper::CollectFChunkAssetsDescribeByChunk( const FChunkInfo& Chunk, TArray Platforms ) { + SCOPED_NAMED_EVENT_TEXT("CollectFChunkAssetsDescribeByChunk",FColor::Red); FChunkAssetDescribe ChunkAssetDescribe; // Collect Chunk Assets - { - - FAssetDependenciesInfo SpecifyDependAssets; - - FAssetDependenciesParser Parser; - FAssetDependencies Conf; - TArray AssetFilterPaths = UFlibAssetManageHelper::DirectoriesToStrings(Chunk.AssetIncludeFilters); - Conf.IncludeFilters = AssetFilterPaths; - Conf.IgnoreFilters = UFlibAssetManageHelper::DirectoriesToStrings(Chunk.AssetIgnoreFilters); - Conf.ForceSkipPackageNames = UFlibAssetManageHelper::SoftObjectPathsToStrings(Chunk.ForceSkipAssets); - Conf.InIncludeSpecifyAsset = Chunk.IncludeSpecifyAssets; - Conf.AssetRegistryDependencyTypes = Chunk.AssetRegistryDependencyTypes; - Conf.AnalysicFilterDependencies = Chunk.bAnalysisFilterDependencies; - Conf.ForceSkipContents = UFlibAssetManageHelper::DirectoriesToStrings(Chunk.ForceSkipContentRules); - Conf.ForceSkipContents.Append(UFlibAssetManageHelper::DirectoriesToStrings(PatcheSettings->GetAssetScanConfig().ForceSkipContentRules)); - - auto AddForceSkipAssets = [&Conf](const TArray& Assets) - { - for(const auto& forceSkipAsset:Assets) - { - Conf.ForceSkipContents.Add(forceSkipAsset.GetLongPackageName()); - } - }; - AddForceSkipAssets(Chunk.ForceSkipAssets); - AddForceSkipAssets(PatcheSettings->GetAssetScanConfig().ForceSkipAssets); - - auto AddSkipClassesLambda = [&Conf](const TArray& Classes) - { - for(const auto& ForceSkipClass:Classes) - { - Conf.IgnoreAseetTypes.Add(*ForceSkipClass->GetName()); - } - }; - AddSkipClassesLambda(Chunk.ForceSkipClasses); - AddSkipClassesLambda(PatcheSettings->GetAssetScanConfig().ForceSkipClasses); - - Parser.Parse(Conf); - TSet AssetLongPackageNames = Parser.GetrParseResults(); - - for(FName LongPackageName:AssetLongPackageNames) - { - if(LongPackageName.IsNone()) - { - continue; - } - AssetFilterPaths.AddUnique(LongPackageName.ToString()); - } - - const FAssetDependenciesInfo& AddAssetsRef = DiffInfo.AssetDiffInfo.AddAssetDependInfo; - const FAssetDependenciesInfo& ModifyAssetsRef = DiffInfo.AssetDiffInfo.ModifyAssetDependInfo; - - - auto CollectChunkAssets = [](const FAssetDependenciesInfo& SearchBase, const TArray& SearchFilters)->FAssetDependenciesInfo - { - FAssetDependenciesInfo ResultAssetDependInfos; - - for (const auto& SearchItem : SearchFilters) - { - if (SearchItem.IsEmpty()) - continue; - - FString SearchModuleName; - int32 findedPos = SearchItem.Find(TEXT("/"), ESearchCase::IgnoreCase, ESearchDir::FromStart, 1); - if (findedPos != INDEX_NONE) - { - SearchModuleName = UKismetStringLibrary::GetSubstring(SearchItem, 1, findedPos - 1); - } - else - { - SearchModuleName = UKismetStringLibrary::GetSubstring(SearchItem, 1, SearchItem.Len() - 1); - } - - if (!SearchModuleName.IsEmpty() && (SearchBase.AssetsDependenciesMap.Contains(SearchModuleName))) - { - if (!ResultAssetDependInfos.AssetsDependenciesMap.Contains(SearchModuleName)) - ResultAssetDependInfos.AssetsDependenciesMap.Add(SearchModuleName, FAssetDependenciesDetail(SearchModuleName, TMap{})); - - const FAssetDependenciesDetail& SearchBaseModule = *SearchBase.AssetsDependenciesMap.Find(SearchModuleName); - - TArray AllAssetKeys; - SearchBaseModule.AssetDependencyDetails.GetKeys(AllAssetKeys); - - for (const auto& KeyItem : AllAssetKeys) - { - if (KeyItem.StartsWith(SearchItem)) - { - FAssetDetail FindedAsset = *SearchBaseModule.AssetDependencyDetails.Find(KeyItem); - if (!ResultAssetDependInfos.AssetsDependenciesMap.Find(SearchModuleName)->AssetDependencyDetails.Contains(KeyItem)) - { - ResultAssetDependInfos.AssetsDependenciesMap.Find(SearchModuleName)->AssetDependencyDetails.Add(KeyItem, FindedAsset); - } - } - } - } - } - return ResultAssetDependInfos; - }; - - ChunkAssetDescribe.AddAssets = CollectChunkAssets(AddAssetsRef, AssetFilterPaths); - ChunkAssetDescribe.ModifyAssets = CollectChunkAssets(ModifyAssetsRef, AssetFilterPaths); + // { + // + // FAssetDependenciesInfo SpecifyDependAssets; + // + // FAssetDependenciesParser Parser; + // FAssetDependencies Conf; + // TArray AssetFilterPaths = UFlibAssetManageHelper::DirectoriesToStrings(Chunk.AssetIncludeFilters); + // Conf.IncludeFilters = AssetFilterPaths; + // Conf.IgnoreFilters = UFlibAssetManageHelper::DirectoriesToStrings(Chunk.AssetIgnoreFilters); + // Conf.ForceSkipPackageNames = UFlibAssetManageHelper::SoftObjectPathsToStrings(Chunk.ForceSkipAssets); + // Conf.InIncludeSpecifyAsset = Chunk.IncludeSpecifyAssets; + // Conf.AssetRegistryDependencyTypes = Chunk.AssetRegistryDependencyTypes; + // Conf.AnalysicFilterDependencies = Chunk.bAnalysisFilterDependencies; + // Conf.ForceSkipContents = UFlibAssetManageHelper::DirectoriesToStrings(Chunk.ForceSkipContentRules); + // Conf.ForceSkipContents.Append(UFlibAssetManageHelper::DirectoriesToStrings(PatcheSettings->GetAssetScanConfig().ForceSkipContentRules)); + // + // auto AddForceSkipAssets = [&Conf](const TArray& Assets) + // { + // for(const auto& forceSkipAsset:Assets) + // { + // Conf.ForceSkipContents.Add(forceSkipAsset.GetLongPackageName()); + // } + // }; + // AddForceSkipAssets(Chunk.ForceSkipAssets); + // AddForceSkipAssets(PatcheSettings->GetAssetScanConfig().ForceSkipAssets); + // + // auto AddSkipClassesLambda = [&Conf](const TArray& Classes) + // { + // for(const auto& ForceSkipClass:Classes) + // { + // Conf.IgnoreAseetTypes.Add(*ForceSkipClass->GetName()); + // } + // }; + // AddSkipClassesLambda(Chunk.ForceSkipClasses); + // AddSkipClassesLambda(PatcheSettings->GetAssetScanConfig().ForceSkipClasses); + // + // Parser.Parse(Conf); + // TSet AssetLongPackageNames = Parser.GetrParseResults(); + // + // for(FName LongPackageName:AssetLongPackageNames) + // { + // if(LongPackageName.IsNone()) + // { + // continue; + // } + // AssetFilterPaths.AddUnique(LongPackageName.ToString()); + // } + // + // const FAssetDependenciesInfo& AddAssetsRef = DiffInfo.AssetDiffInfo.AddAssetDependInfo; + // const FAssetDependenciesInfo& ModifyAssetsRef = DiffInfo.AssetDiffInfo.ModifyAssetDependInfo; + // + // + // auto CollectChunkAssets = [](const FAssetDependenciesInfo& SearchBase, const TArray& SearchFilters)->FAssetDependenciesInfo + // { + // SCOPED_NAMED_EVENT_TEXT("CollectChunkAssets",FColor::Red); + // FAssetDependenciesInfo ResultAssetDependInfos; + // + // for (const auto& SearchItem : SearchFilters) + // { + // if (SearchItem.IsEmpty()) + // continue; + // + // FString SearchModuleName; + // int32 findedPos = SearchItem.Find(TEXT("/"), ESearchCase::IgnoreCase, ESearchDir::FromStart, 1); + // if (findedPos != INDEX_NONE) + // { + // SearchModuleName = UKismetStringLibrary::GetSubstring(SearchItem, 1, findedPos - 1); + // } + // else + // { + // SearchModuleName = UKismetStringLibrary::GetSubstring(SearchItem, 1, SearchItem.Len() - 1); + // } + // + // if (!SearchModuleName.IsEmpty() && (SearchBase.AssetsDependenciesMap.Contains(SearchModuleName))) + // { + // if (!ResultAssetDependInfos.AssetsDependenciesMap.Contains(SearchModuleName)) + // ResultAssetDependInfos.AssetsDependenciesMap.Add(SearchModuleName, FAssetDependenciesDetail(SearchModuleName, TMap{})); + // + // const FAssetDependenciesDetail& SearchBaseModule = *SearchBase.AssetsDependenciesMap.Find(SearchModuleName); + // + // TArray AllAssetKeys; + // SearchBaseModule.AssetDependencyDetails.GetKeys(AllAssetKeys); + // + // for (const auto& KeyItem : AllAssetKeys) + // { + // if (KeyItem.StartsWith(SearchItem)) + // { + // FAssetDetail FindedAsset = *SearchBaseModule.AssetDependencyDetails.Find(KeyItem); + // if (!ResultAssetDependInfos.AssetsDependenciesMap.Find(SearchModuleName)->AssetDependencyDetails.Contains(KeyItem)) + // { + // ResultAssetDependInfos.AssetsDependenciesMap.Find(SearchModuleName)->AssetDependencyDetails.Add(KeyItem, FindedAsset); + // } + // } + // } + // } + // } + // return ResultAssetDependInfos; + // }; + //} + { + ChunkAssetDescribe.AddAssets = DiffInfo.AssetDiffInfo.AddAssetDependInfo; // CollectChunkAssets(AddAssetsRef, AssetFilterPaths); + ChunkAssetDescribe.ModifyAssets = DiffInfo.AssetDiffInfo.ModifyAssetDependInfo; //CollectChunkAssets(ModifyAssetsRef, AssetFilterPaths); ChunkAssetDescribe.Assets = UFlibAssetManageHelper::CombineAssetDependencies(ChunkAssetDescribe.AddAssets, ChunkAssetDescribe.ModifyAssets); } auto CoolectPlatformExFiles = [](const FPatchVersionDiff& DiffInfo,const auto& Chunk,ETargetPlatform Platform)->TArray { + SCOPED_NAMED_EVENT_TEXT("CoolectPlatformExFiles",FColor::Red); // Collect Extern Files TArray AllFiles; @@ -1281,6 +1285,7 @@ FHotPatcherVersion UFlibPatchParserHelper::ExportReleaseVersionInfoByChunk( } FAssetScanConfig ScanConfig; + ScanConfig.bPackageTracker = false; ScanConfig.AssetIncludeFilters = InChunkInfo.AssetIncludeFilters; ScanConfig.AssetIgnoreFilters = InChunkInfo.AssetIgnoreFilters; ScanConfig.bAnalysisFilterDependencies = bInAnalysisFilterDependencies; @@ -2137,3 +2142,60 @@ bool UFlibPatchParserHelper::IsValidPatchSettings(const FExportPatchSettings* Pa } return bCanExport; } + +FString UFlibPatchParserHelper::GetTargetPlatformsCmdLine(const TArray& Platforms) +{ + FString Result; + if(Platforms.Num()) + { + FString PlatformArrayStr; + for(ETargetPlatform Platform:Platforms) + { + FString PlatformName = THotPatcherTemplateHelper::GetEnumNameByValue(Platform); + PlatformArrayStr += FString::Printf(TEXT("%s+"),*PlatformName); + } + PlatformArrayStr.RemoveFromEnd(TEXT("+")); + if(!PlatformArrayStr.IsEmpty()) + { + Result = FString::Printf(TEXT("-TargetPlatform=%s"),*PlatformArrayStr); + } + } + return Result; +} + +void UFlibPatchParserHelper::SetPropertyTransient(UStruct* Struct,const FString& PropertyName,bool bTransient) +{ + for(TFieldIterator PropertyIter(Struct);PropertyIter;++PropertyIter) + { + FProperty* PropertyIns = *PropertyIter; + if(PropertyName.Equals(*PropertyIns->GetName(),ESearchCase::IgnoreCase)) + { + if(bTransient) + { + PropertyIns->SetPropertyFlags(CPF_Transient); + } + else + { + PropertyIns->ClearPropertyFlags(CPF_Transient); + } + break; + } + } +} + +FString UFlibPatchParserHelper::MergeOptionsAsCmdline(const TArray& InOptions) +{ + FString Cmdline; + for(const auto& Option:InOptions) + { + FString InOption = Option; + while(InOption.RemoveFromStart(TEXT(" "))){} + while(InOption.RemoveFromEnd(TEXT(" "))){} + if(!InOption.IsEmpty()) + { + Cmdline += FString::Printf(TEXT("%s "),*InOption); + } + } + Cmdline.RemoveFromEnd(TEXT(" ")); + return Cmdline; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPackageTracker.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPackageTracker.h index c76f6142..195d94da 100644 --- a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPackageTracker.h +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPackageTracker.h @@ -111,3 +111,36 @@ struct FPackageTracker : public FPackageTrackerBase TSet PackagesPendingSave; TSet& ExisitAssets; }; + +struct FClassesPackageTracker : public FPackageTrackerBase +{ + virtual void OnPackageCreated(UPackage* Package) override + { + FName ClassName = UFlibAssetManageHelper::GetAssetTypeByPackage(Package); + + if(!ClassMapping.Contains(ClassName)) + { + ClassMapping.Add(ClassName,TArray{}); + } + ClassMapping.Find(ClassName)->AddUnique(Package); + }; + virtual void OnPackageDeleted(UPackage* Package) override + { + FName ClassName = UFlibAssetManageHelper::GetAssetTypeByPackage(Package); + if(ClassMapping.Contains(ClassName)) + { + ClassMapping.Find(ClassName)->Remove(Package); + } + } + TArray GetPackagesByClassName(FName ClassName) + { + TArray result; + if(ClassMapping.Contains(ClassName)) + { + result = *ClassMapping.Find(ClassName); + } + return result; + } +protected: + TMap> ClassMapping; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/FExportPatchSettings.h b/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/FExportPatchSettings.h index 1909249f..4247a43d 100644 --- a/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/FExportPatchSettings.h +++ b/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/FExportPatchSettings.h @@ -124,6 +124,8 @@ struct HOTPATCHERRUNTIME_API FExportPatchSettings:public FHotPatcherSettingBase FORCEINLINE FCookShaderOptions GetCookShaderOptions()const {return CookShaderOptions;} FORCEINLINE FAssetRegistryOptions GetSerializeAssetRegistryOptions()const{return SerializeAssetRegistryOptions;} FORCEINLINE bool IsImportProjectSettings()const{ return bImportProjectSettings; } + + virtual FString GetCombinedAdditionalCommandletArgs()const override; public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BaseVersion") bool bByBaseVersion = false; diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/HotPatcherSettingBase.h b/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/HotPatcherSettingBase.h index 3ecd99de..18e2d824 100644 --- a/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/HotPatcherSettingBase.h +++ b/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/HotPatcherSettingBase.h @@ -37,15 +37,7 @@ struct HOTPATCHERRUNTIME_API FHotPatcherSettingBase:public FPatcherEntitySetting FORCEINLINE virtual bool IsStandaloneMode()const {return bStandaloneMode;} FORCEINLINE virtual bool IsSaveConfig()const {return bStorageConfig;} FORCEINLINE virtual TArray GetAdditionalCommandletArgs()const{return AdditionalCommandletArgs;} - FORCEINLINE virtual FString GetCombinedAdditionalCommandletArgs()const - { - FString Result; - for(const auto& Option:GetAdditionalCommandletArgs()) - { - Result+=FString::Printf(TEXT("%s "),*Option); - } - return Result; - } + virtual FString GetCombinedAdditionalCommandletArgs()const; FORCEINLINE virtual bool IsForceSkipContent()const{return GetAssetScanConfig().bForceSkipContent;} FORCEINLINE virtual TArray GetForceSkipContentRules()const {return GetAssetScanConfig().ForceSkipContentRules;} diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/FlibAssetManageHelper.h b/HotPatcher/Source/HotPatcherRuntime/Public/FlibAssetManageHelper.h index 9a903030..fd1baba1 100644 --- a/HotPatcher/Source/HotPatcherRuntime/Public/FlibAssetManageHelper.h +++ b/HotPatcher/Source/HotPatcherRuntime/Public/FlibAssetManageHelper.h @@ -86,7 +86,9 @@ class HOTPATCHERRUNTIME_API UFlibAssetManageHelper : public UBlueprintFunctionLi static FAssetPackageData* GetPackageDataByPackageName(const FString& InPackageName); UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|AssetManagerExEx") static bool GetAssetPackageGUID(const FString& InPackageName, FName& OutGUID); - + + static FSoftObjectPath CreateSoftObjectPathByPackage(UPackage* Package); + static FName GetAssetTypeByPackage(UPackage* Package); static FName GetAssetType(FSoftObjectPath InPackageName); // Combine AssetDependencies Filter repeat asset @@ -251,6 +253,8 @@ class HOTPATCHERRUNTIME_API UFlibAssetManageHelper : public UBlueprintFunctionLi static FName GetAssetDataClasses(const FAssetData& Data); static FName GetObjectPathByAssetData(const FAssetData& Data); static bool bIncludeOnlyOnDiskAssets; + + static void UpdateAssetRegistryData(const FString& PackageName); }; diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/FlibPatchParserHelper.h b/HotPatcher/Source/HotPatcherRuntime/Public/FlibPatchParserHelper.h index b8a8e0c6..f97c2a7f 100644 --- a/HotPatcher/Source/HotPatcherRuntime/Public/FlibPatchParserHelper.h +++ b/HotPatcher/Source/HotPatcherRuntime/Public/FlibPatchParserHelper.h @@ -239,6 +239,8 @@ class HOTPATCHERRUNTIME_API UFlibPatchParserHelper : public UBlueprintFunctionLi } static bool IsValidPatchSettings(const FExportPatchSettings* PatchSettings,bool bExternalFilesCheck); - + static void SetPropertyTransient(UStruct* Struct,const FString& PropertyName,bool bTransient); + static FString GetTargetPlatformsCmdLine(const TArray& Platforms); + static FString MergeOptionsAsCmdline(const TArray& InOptions); }; diff --git a/Mods/HotChunker b/Mods/HotChunker index b6b02dc5..cf6424b3 160000 --- a/Mods/HotChunker +++ b/Mods/HotChunker @@ -1 +1 @@ -Subproject commit b6b02dc5956ea58d0d2581da76fe4419b667af21 +Subproject commit cf6424b35ebdfcd248341d4187fff444eaa75d96 diff --git a/Mods/HotMultiCooker b/Mods/HotMultiCooker index dff1f4a9..ccdd34e1 160000 --- a/Mods/HotMultiCooker +++ b/Mods/HotMultiCooker @@ -1 +1 @@ -Subproject commit dff1f4a9b606b4a74f0940f0bbabd8787bfa7fb9 +Subproject commit ccdd34e12625708b8a562b2430dcde3aa44cc7f9 From 87fcf4b08bdce07d08dfc5f864f2b7736890050f Mon Sep 17 00:00:00 2001 From: hxhb Date: Fri, 9 Dec 2022 12:51:59 +0000 Subject: [PATCH 07/81] optimize impl --- .../Cooker/MultiCooker/SingleCookerProxy.cpp | 17 +++++-- .../Private/CreatePatch/PatcherProxy.cpp | 22 +++++++-- .../Private/FlibHotPatcherCoreHelper.cpp | 36 +++++++++++---- .../Public/FlibHotPatcherCoreHelper.h | 17 +++++-- .../CreatePatch/SHotPatcherPatchWidget.cpp | 23 ++-------- .../Private/FlibHotPatcherEditorHelper.cpp | 45 +++++++++++++++++++ .../Private/HotPatcherEditor.cpp | 42 +++-------------- .../Private/SHotPatcherWidgetBase.cpp | 23 +++------- .../Public/FlibHotPatcherEditorHelper.h | 4 +- .../CreatePatch/FExportPatchSettings.cpp | 10 ++--- .../CreatePatch/HotPatcherSettingBase.cpp | 12 ++--- .../Private/FlibAssetManageHelper.cpp | 11 +++-- .../Public/CreatePatch/FExportPatchSettings.h | 11 +++-- Mods/HotChunker | 2 +- 14 files changed, 160 insertions(+), 115 deletions(-) diff --git a/HotPatcher/Source/HotPatcherCore/Private/Cooker/MultiCooker/SingleCookerProxy.cpp b/HotPatcher/Source/HotPatcherCore/Private/Cooker/MultiCooker/SingleCookerProxy.cpp index e741717c..168370fb 100644 --- a/HotPatcher/Source/HotPatcherCore/Private/Cooker/MultiCooker/SingleCookerProxy.cpp +++ b/HotPatcher/Source/HotPatcherCore/Private/Cooker/MultiCooker/SingleCookerProxy.cpp @@ -49,7 +49,7 @@ void FFreezePackageTracker::NotifyUObjectCreated(const UObjectBase* Object, int3 } } } - +#define NO_POSTLOAD_CACHE_DDC_OPTION TEXT("-NoPostLoadCacheDDC") void USingleCookerProxy::Init(FPatcherEntitySettingBase* InSetting) { SCOPED_NAMED_EVENT_TEXT("Init",FColor::Red); @@ -58,6 +58,12 @@ void USingleCookerProxy::Init(FPatcherEntitySettingBase* InSetting) // StreamableDelegateDelayFrames->Set(0); UFlibHotPatcherCoreHelper::DumpActiveTargetPlatforms(); + FString Cmdline = FCommandLine::Get(); + if(!Cmdline.Contains(NO_POSTLOAD_CACHE_DDC_OPTION,ESearchCase::IgnoreCase)) + { + FCommandLine::Append(*FString::Printf(TEXT(" %s"),NO_POSTLOAD_CACHE_DDC_OPTION)); + UE_LOG(LogHotPatcher,Display,TEXT("Append %s to Cmdline."),NO_POSTLOAD_CACHE_DDC_OPTION); + } #if WITH_PACKAGE_CONTEXT if(GetSettingObject()->bOverrideSavePackageContext) { @@ -595,10 +601,12 @@ FCookCluster USingleCookerProxy::GetPackageTrackerAsCluster() if(PackageTracker && GetSettingObject()->bCookPackageTrackerAssets) { PackageTrackerCluster.AssetDetails.Empty(); - for(FName PackagePath:PackageTracker->GetPendingPackageSet()) + for(FName LongPackageName:PackageTracker->GetPendingPackageSet()) { // make asset data to asset registry - FSoftObjectPath ObjectPath(PackagePath.ToString()); + FSoftObjectPath ObjectPath( + UFlibAssetManageHelper::LongPackageNameToPackagePath(LongPackageName.ToString()) + ); UFlibAssetManageHelper::UpdateAssetRegistryData(ObjectPath.GetLongPackageName()); FAssetData AssetData; @@ -612,7 +620,7 @@ FCookCluster USingleCookerProxy::GetPackageTrackerAsCluster() } else { - UE_LOG(LogHotPatcher,Warning,TEXT("[GetPackageTrackerAsCluster] Get %s AssetData Failed!"),*PackagePath.ToString()); + UE_LOG(LogHotPatcher,Warning,TEXT("[GetPackageTrackerAsCluster] Get %s AssetData Failed!"),*LongPackageName.ToString()); } } } @@ -689,6 +697,7 @@ bool USingleCookerProxy::DoExport() void USingleCookerProxy::CleanOldCooked(const FString& CookBaseDir,const TArray& ObjectPaths,const TArray& CookPlatforms) { + SCOPED_NAMED_EVENT_TEXT("CleanOldCooked",FColor::Red); TArray CookPlatfotms = UFlibHotPatcherCoreHelper::GetTargetPlatformsByNames(CookPlatforms); { SCOPED_NAMED_EVENT_TEXT("Delete Old Cooked Files",FColor::Red); diff --git a/HotPatcher/Source/HotPatcherCore/Private/CreatePatch/PatcherProxy.cpp b/HotPatcher/Source/HotPatcherCore/Private/CreatePatch/PatcherProxy.cpp index 9eb01bec..4a51b5da 100644 --- a/HotPatcher/Source/HotPatcherCore/Private/CreatePatch/PatcherProxy.cpp +++ b/HotPatcher/Source/HotPatcherCore/Private/CreatePatch/PatcherProxy.cpp @@ -589,7 +589,14 @@ namespace PatchWorker EmptySetting.NumberOfAssetsPerFrame = 200; EmptySetting.bDisplayConfig = false; EmptySetting.StorageCookedDir = Context.GetSettingObject()->GetStorageCookedDir();//FPaths::Combine(FPaths::ConvertRelativePathToFull(FPaths::ProjectSavedDir()),TEXT("Cooked")); - EmptySetting.StorageMetadataDir = FPaths::Combine(Context.GetSettingObject()->GetSaveAbsPath(),Context.CurrentVersion.VersionId,TEXT("Metadatas"),Chunk.ChunkName); + FReplacePakRegular PakSaveDirRegular{ + Context.CurrentVersion.VersionId, + Context.CurrentVersion.BaseVersionId, + Chunk.ChunkName, + PlatformName + }; + FString ReplacedPakSaveDirRegular = UFlibHotPatcherCoreHelper::ReplacePakRegular(PakSaveDirRegular,Context.GetSettingObject()->GetPakSaveDirRegular()); + EmptySetting.StorageMetadataDir = FPaths::Combine(Context.GetSettingObject()->GetSaveAbsPath(),ReplacedPakSaveDirRegular,TEXT("Metadatas")); #if WITH_PACKAGE_CONTEXT EmptySetting.bOverrideSavePackageContext = true; EmptySetting.PlatformSavePackageContexts = Context.PatchProxy->GetPlatformSavePackageContexts(); @@ -868,7 +875,14 @@ namespace PatchWorker Context.OnPaking.Broadcast(TEXT("ExportPatch"),*Dialog.ToString()); Context.UnrealPakSlowTask->EnterProgressFrame(1.0, Dialog); } - FString ChunkSaveBasePath = FPaths::Combine(Context.GetSettingObject()->GetSaveAbsPath(), Context.CurrentVersion.VersionId, PlatformName); + FReplacePakRegular PakSaveDirRegular{ + Context.CurrentVersion.VersionId, + Context.CurrentVersion.BaseVersionId, + Chunk.ChunkName, + PlatformName + }; + FString ReplacedPakSaveDirRegular = UFlibHotPatcherCoreHelper::ReplacePakRegular(PakSaveDirRegular,Context.GetSettingObject()->GetPakSaveDirRegular()); + FString ChunkSaveBasePath = FPaths::Combine(Context.GetSettingObject()->GetSaveAbsPath(),ReplacedPakSaveDirRegular); TArray ChunkPakListCommands; { @@ -964,8 +978,8 @@ namespace PatchWorker SinglePakForChunk.PakCommands = ChunkPakListCommands; // add extern file to pak(version file) SinglePakForChunk.PakCommands.Append(Context.AdditionalFileToPak); - - const FString ChunkPakName = UFlibHotPatcherCoreHelper::MakePakShortName(Context.CurrentVersion,Chunk,PlatformName,Context.GetSettingObject()->GetPakNameRegular()); + + const FString ChunkPakName = UFlibHotPatcherCoreHelper::ReplacePakRegular(PakSaveDirRegular,Context.GetSettingObject()->GetPakNameRegular()); SinglePakForChunk.ChunkStoreName = ChunkPakName; SinglePakForChunk.StorageDirectory = ChunkSaveBasePath; Chunk.GetPakFileProxys().Add(SinglePakForChunk); diff --git a/HotPatcher/Source/HotPatcherCore/Private/FlibHotPatcherCoreHelper.cpp b/HotPatcher/Source/HotPatcherCore/Private/FlibHotPatcherCoreHelper.cpp index c8f63764..b4df2797 100644 --- a/HotPatcher/Source/HotPatcherCore/Private/FlibHotPatcherCoreHelper.cpp +++ b/HotPatcher/Source/HotPatcherCore/Private/FlibHotPatcherCoreHelper.cpp @@ -949,8 +949,7 @@ FString UFlibHotPatcherCoreHelper::PatchSummary(const FPatchVersionDiff& DiffInf return result; } - -FString UFlibHotPatcherCoreHelper::MakePakShortName(const FHotPatcherVersion& InCurrentVersion, const FChunkInfo& InChunkInfo, const FString& InPlatform,const FString& InRegular) +FString UFlibHotPatcherCoreHelper::ReplacePakRegular(const FReplacePakRegular& RegularConf, const FString& InRegular) { struct FResularOperator { @@ -961,15 +960,21 @@ FString UFlibHotPatcherCoreHelper::MakePakShortName(const FHotPatcherVersion& In }; TArray RegularOpList; - RegularOpList.Emplace(TEXT("{VERSION}"),[&InCurrentVersion]()->FString{return InCurrentVersion.VersionId;}); - RegularOpList.Emplace(TEXT("{BASEVERSION}"),[&InCurrentVersion]()->FString{return InCurrentVersion.BaseVersionId;}); - RegularOpList.Emplace(TEXT("{PLATFORM}"),[&InPlatform]()->FString{return InPlatform;}); - RegularOpList.Emplace(TEXT("{CHUNKNAME}"),[&InChunkInfo,&InCurrentVersion]()->FString + RegularOpList.Emplace(TEXT("{VERSION}"),[&RegularConf]()->FString{return RegularConf.VersionId;}); + RegularOpList.Emplace(TEXT("{BASEVERSION}"),[&RegularConf]()->FString{return RegularConf.BaseVersionId;}); + RegularOpList.Emplace(TEXT("{PLATFORM}"),[&RegularConf]()->FString{return RegularConf.PlatformName;}); + RegularOpList.Emplace(TEXT("{CHUNKNAME}"),[&RegularConf,InRegular]()->FString { - if(!InCurrentVersion.VersionId.Equals(InChunkInfo.ChunkName)) - return InChunkInfo.ChunkName; - else + if(InRegular.Contains(TEXT("{VERSION}")) && + InRegular.Contains(TEXT("{CHUNKNAME}")) && + RegularConf.VersionId.Equals(RegularConf.ChunkName)) + { return TEXT(""); + } + else + { + return RegularConf.ChunkName; + } }); auto CustomPakNameRegular = [](const TArray& Operators,const FString& Regular)->FString @@ -1155,6 +1160,7 @@ ITargetPlatform* UFlibHotPatcherCoreHelper::GetPlatformByName(const FString& Nam FPatchVersionDiff UFlibHotPatcherCoreHelper::DiffPatchVersionWithPatchSetting(const FExportPatchSettings& PatchSetting, const FHotPatcherVersion& Base, const FHotPatcherVersion& New) { + SCOPED_NAMED_EVENT_TEXT("DiffPatchVersionWithPatchSetting",FColor::Red); FPatchVersionDiff VersionDiffInfo; const FAssetDependenciesInfo& BaseVersionAssetDependInfo = Base.AssetInfo; const FAssetDependenciesInfo& CurrentVersionAssetDependInfo = New.AssetInfo; @@ -2698,4 +2704,16 @@ void UFlibHotPatcherCoreHelper::DumpActiveTargetPlatforms() ActiveTargetPlatforms.RemoveFromEnd(TEXT(",")); } UE_LOG(LogHotPatcherCoreHelper,Display,TEXT("[IMPORTTENT] ActiveTargetPlatforms: %s"),*ActiveTargetPlatforms); +} + +FString UFlibHotPatcherCoreHelper::GetPlatformsStr(TArray Platforms) +{ + FString result; + for(auto Platform:Platforms) + { + FString PlatformStr = THotPatcherTemplateHelper::GetEnumNameByValue(Platform,false); + result+=FString::Printf(TEXT("%s,"),*PlatformStr); + } + result.RemoveFromEnd(TEXT(",")); + return result; } \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Public/FlibHotPatcherCoreHelper.h b/HotPatcher/Source/HotPatcherCore/Public/FlibHotPatcherCoreHelper.h index 1777d82d..dae45068 100644 --- a/HotPatcher/Source/HotPatcherCore/Public/FlibHotPatcherCoreHelper.h +++ b/HotPatcher/Source/HotPatcherCore/Public/FlibHotPatcherCoreHelper.h @@ -59,6 +59,17 @@ struct FProjectPackageAssetCollection TArray NeverCookPackages; }; + +struct HOTPATCHERCORE_API FReplacePakRegular +{ + FReplacePakRegular()=default; + FReplacePakRegular(const FString& InVersionId,const FString& InBaseVersionId,const FString& InChunkName,const FString& InPlatformName): + VersionId(InVersionId),BaseVersionId(InBaseVersionId),ChunkName(InChunkName),PlatformName(InPlatformName){} + FString VersionId; + FString BaseVersionId; + FString ChunkName; + FString PlatformName; +}; /** * */ @@ -161,7 +172,8 @@ class HOTPATCHERCORE_API UFlibHotPatcherCoreHelper : public UBlueprintFunctionLi static FString ReleaseSummary(const FHotPatcherVersion& NewVersion); static FString PatchSummary(const FPatchVersionDiff& DiffInfo); - static FString MakePakShortName(const FHotPatcherVersion& InCurrentVersion, const FChunkInfo& InChunkInfo, const FString& InPlatform,const FString& InRegular); + + static FString ReplacePakRegular(const FReplacePakRegular& RegularConf, const FString& InRegular); static bool CheckSelectedAssetsCookStatus(const FString& OverrideCookedDir,const TArray& PlatformNames, const FAssetDependenciesInfo& SelectedAssets, FString& OutMsg); static bool CheckPatchRequire(const FString& OverrideCookedDir,const FPatchVersionDiff& InDiff,const TArray& PlatformNames,FString& OutMsg); @@ -277,8 +289,6 @@ class HOTPATCHERCORE_API UFlibHotPatcherCoreHelper : public UBlueprintFunctionLi static void AdaptorOldVersionConfig(FAssetScanConfig& ScanConfig,const FString& JsonContent); static bool GetIniPlatformName(const FString& PlatformName,FString& OutIniPlatformName); - - // need add UNREALED_API to FAssetRegistryGenerator // all chunksinfo.csv / pakchunklist.txt / assetregistry.bin @@ -291,4 +301,5 @@ class HOTPATCHERCORE_API UFlibHotPatcherCoreHelper : public UBlueprintFunctionLi static TSet GetAllMaterialClassesNames(); static TArray GetPreCacheClasses(); static void DumpActiveTargetPlatforms(); + static FString GetPlatformsStr(TArray Platforms); }; diff --git a/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/SHotPatcherPatchWidget.cpp b/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/SHotPatcherPatchWidget.cpp index 899febf5..d6c56727 100644 --- a/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/SHotPatcherPatchWidget.cpp +++ b/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/SHotPatcherPatchWidget.cpp @@ -476,25 +476,10 @@ bool SHotPatcherPatchWidget::CanExportPatch()const FReply SHotPatcherPatchWidget::DoExportPatch() { - if(!GetConfigSettings()->IsStandaloneMode()) - { - UPatcherProxy* PatcherProxy = NewObject(); - PatcherProxy->AddToRoot(); - PatcherProxy->Init(ExportPatchSetting.Get()); - PatcherProxy->OnShowMsg.AddRaw(this,&SHotPatcherPatchWidget::ShowMsg); - PatcherProxy->DoExport(); - } - else - { - FString CurrentConfig; - THotPatcherTemplateHelper::TSerializeStructAsJsonString(*GetConfigSettings(),CurrentConfig); - FString SaveConfigTo = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectSavedDir(),TEXT("HotPatcher"),TEXT("PatchConfig.json"))); - FFileHelper::SaveStringToFile(CurrentConfig,*SaveConfigTo); - FString ProfilingCmd = GetConfigSettings()->IsEnableProfiling() ? TEXT("-trace=cpu,loadtimetrace") : TEXT(""); - FString MissionCommand = FString::Printf(TEXT("\"%s\" -run=HotPatcher -config=\"%s\" %s %s"),*UFlibPatchParserHelper::GetProjectFilePath(),*SaveConfigTo,*GetConfigSettings()->GetCombinedAdditionalCommandletArgs(),*ProfilingCmd); - UE_LOG(LogHotPatcher,Log,TEXT("HotPatcher %s Mission: %s %s"),*GetMissionName(),*UFlibHotPatcherCoreHelper::GetUECmdBinary(),*MissionCommand); - FHotPatcherEditorModule::Get().RunProcMission(UFlibHotPatcherCoreHelper::GetUECmdBinary(),MissionCommand,GetMissionName()); - } + TSharedPtr PatchSettings = MakeShareable(new FExportPatchSettings); + *PatchSettings = *GetConfigSettings(); + FHotPatcherEditorModule::Get().CookAndPakByPatchSettings(PatchSettings,PatchSettings->IsStandaloneMode()); + return FReply::Handled(); } diff --git a/HotPatcher/Source/HotPatcherEditor/Private/FlibHotPatcherEditorHelper.cpp b/HotPatcher/Source/HotPatcherEditor/Private/FlibHotPatcherEditorHelper.cpp index af12fa9c..7afc3d6f 100644 --- a/HotPatcher/Source/HotPatcherEditor/Private/FlibHotPatcherEditorHelper.cpp +++ b/HotPatcher/Source/HotPatcherEditor/Private/FlibHotPatcherEditorHelper.cpp @@ -1,4 +1,7 @@ #include "FlibHotPatcherEditorHelper.h" + +#include "DesktopPlatformModule.h" +#include "IDesktopPlatform.h" #include "Async/Async.h" void UFlibHotPatcherEditorHelper::CreateSaveFileNotify(const FText& InMsg, const FString& InSavedFile, @@ -25,3 +28,45 @@ void UFlibHotPatcherEditorHelper::CreateSaveFileNotify(const FText& InMsg, const FSlateNotificationManager::Get().AddNotification(Info)->SetCompletionState(NotifyType); }); } + +TArray UFlibHotPatcherEditorHelper::SaveFileDialog(const FString& DialogTitle, const FString& DefaultPath, + const FString& DefaultFile, const FString& FileTypes, uint32 Flags) +{ + IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); + + TArray SaveFilenames; + if (DesktopPlatform) + { + const bool bOpened = DesktopPlatform->SaveFileDialog( + nullptr, + DialogTitle, + DefaultPath, + DefaultFile, + FileTypes, + Flags, + SaveFilenames + ); + } + return SaveFilenames; +} + +TArray UFlibHotPatcherEditorHelper::OpenFileDialog(const FString& DialogTitle, const FString& DefaultPath, + const FString& DefaultFile, const FString& FileTypes, uint32 Flags) +{ + IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); + TArray SelectedFiles; + + if (DesktopPlatform) + { + const bool bOpened = DesktopPlatform->OpenFileDialog( + nullptr, + DialogTitle, + DefaultPath, + DefaultFile, + FileTypes, + Flags, + SelectedFiles + ); + } + return SelectedFiles; +} diff --git a/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherEditor.cpp b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherEditor.cpp index fe61a80f..6ef5729c 100644 --- a/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherEditor.cpp +++ b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherEditor.cpp @@ -454,29 +454,9 @@ void FHotPatcherEditorModule::OnAddToPatchSettings(const FToolMenuContext& MenuC void FHotPatcherEditorModule::OnPakPreset(FExportPatchSettings Config) { - UHotPatcherSettings* Settings = GetMutableDefault(); - Settings->ReloadConfig(); - - UPatcherProxy* PatcherProxy = NewObject(); - PatcherProxy->AddToRoot(); - Proxys.Add(PatcherProxy); - - PatcherProxy->Init(&Config); - - if(!Config.IsStandaloneMode()) - { - PatcherProxy->DoExport(); - } - else - { - FString CurrentConfig; - THotPatcherTemplateHelper::TSerializeStructAsJsonString(Config,CurrentConfig); - FString SaveConfigTo = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectSavedDir(),TEXT("HotPatcher"),TEXT("PatchConfig.json"))); - FFileHelper::SaveStringToFile(CurrentConfig,*SaveConfigTo); - FString MissionCommand = FString::Printf(TEXT("\"%s\" -run=HotPatcher -config=\"%s\" %s"),*UFlibPatchParserHelper::GetProjectFilePath(),*SaveConfigTo,*Config.GetCombinedAdditionalCommandletArgs()); - UE_LOG(LogHotPatcher,Log,TEXT("HotPatcher %s Mission: %s %s"),*Config.VersionId,*UFlibHotPatcherCoreHelper::GetUECmdBinary(),*MissionCommand); - RunProcMission(UFlibHotPatcherCoreHelper::GetUECmdBinary(),MissionCommand,FString::Printf(TEXT("Mission: %s"),*Config.VersionId)); - } + TSharedPtr PatchSettings = MakeShareable(new FExportPatchSettings); + *PatchSettings = Config; + CookAndPakByPatchSettings(PatchSettings,PatchSettings->IsStandaloneMode()); } #endif @@ -568,17 +548,7 @@ void FHotPatcherEditorModule::CookAndPakByAssetsAndFilters(TArray{},Platforms,true); CookAndPakByPatchSettings(PatchSettings,bForceStandalone); } -FString GetPlatformsStr(TArray Platforms) -{ - FString result; - for(auto Platform:Platforms) - { - FString PlatformStr = THotPatcherTemplateHelper::GetEnumNameByValue(Platform,false); - result+=FString::Printf(TEXT("%s,"),*PlatformStr); - } - result.RemoveFromEnd(TEXT(",")); - return result; -} + void FHotPatcherEditorModule::CookAndPakByPatchSettings(TSharedPtr InPatchSettings,bool bForceStandalone) { @@ -586,12 +556,12 @@ void FHotPatcherEditorModule::CookAndPakByPatchSettings(TSharedPtrVersionId))); FFileHelper::SaveStringToFile(CurrentConfig,*SaveConfigTo); FString MissionCommand = FString::Printf(TEXT("\"%s\" -run=HotPatcher -config=\"%s\" %s"),*UFlibPatchParserHelper::GetProjectFilePath(),*SaveConfigTo,*InPatchSettings->GetCombinedAdditionalCommandletArgs()); UE_LOG(LogHotPatcher,Log,TEXT("HotPatcher %s Mission: %s %s"),*InPatchSettings->VersionId,*UFlibHotPatcherCoreHelper::GetUECmdBinary(),*MissionCommand); - FText DisplayText = UKismetTextLibrary::Conv_StringToText(FString::Printf(TEXT("Packaging %s for %s..."),*InPatchSettings->VersionId,*GetPlatformsStr(InPatchSettings->PakTargetPlatforms))); + FText DisplayText = UKismetTextLibrary::Conv_StringToText(FString::Printf(TEXT("Packaging %s for %s..."),*InPatchSettings->VersionId,*UFlibHotPatcherCoreHelper::GetPlatformsStr(InPatchSettings->PakTargetPlatforms))); RunProcMission(UFlibHotPatcherCoreHelper::GetUECmdBinary(),MissionCommand,InPatchSettings->VersionId,DisplayText); } else diff --git a/HotPatcher/Source/HotPatcherEditor/Private/SHotPatcherWidgetBase.cpp b/HotPatcher/Source/HotPatcherEditor/Private/SHotPatcherWidgetBase.cpp index 5468b90e..3e7c1764 100644 --- a/HotPatcher/Source/HotPatcherEditor/Private/SHotPatcherWidgetBase.cpp +++ b/HotPatcher/Source/HotPatcherEditor/Private/SHotPatcherWidgetBase.cpp @@ -10,6 +10,7 @@ // engine header #include "IDesktopPlatform.h" #include "DesktopPlatformModule.h" +#include "FlibHotPatcherEditorHelper.h" #include "Misc/FileHelper.h" #include "Widgets/Input/SHyperlink.h" @@ -44,42 +45,28 @@ FText SHotPatcherWidgetInterface::GetGenerateTooltipText() const TArray SHotPatcherWidgetInterface::OpenFileDialog()const { - IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); TArray SelectedFiles; - - if (DesktopPlatform) - { - const bool bOpened = DesktopPlatform->OpenFileDialog( - nullptr, + SelectedFiles = UFlibHotPatcherEditorHelper::OpenFileDialog( LOCTEXT("OpenHotPatchConfigDialog", "Open .json").ToString(), FString(TEXT("")), TEXT(""), TEXT("HotPatcher json (*.json)|*.json"), - EFileDialogFlags::None, - SelectedFiles + EFileDialogFlags::None ); - } return SelectedFiles; } TArray SHotPatcherWidgetInterface::SaveFileDialog()const { - IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); - TArray SaveFilenames; - if (DesktopPlatform) - { - const bool bOpened = DesktopPlatform->SaveFileDialog( - nullptr, + SaveFilenames = UFlibHotPatcherEditorHelper::SaveFileDialog( LOCTEXT("SvedHotPatcherConfig", "Save .json").ToString(), FString(TEXT("")), TEXT(""), TEXT("HotPatcher json (*.json)|*.json"), - EFileDialogFlags::None, - SaveFilenames + EFileDialogFlags::None ); - } return SaveFilenames; } diff --git a/HotPatcher/Source/HotPatcherEditor/Public/FlibHotPatcherEditorHelper.h b/HotPatcher/Source/HotPatcherEditor/Public/FlibHotPatcherEditorHelper.h index 2461b3d3..e9207228 100644 --- a/HotPatcher/Source/HotPatcherEditor/Public/FlibHotPatcherEditorHelper.h +++ b/HotPatcher/Source/HotPatcherEditor/Public/FlibHotPatcherEditorHelper.h @@ -3,6 +3,7 @@ // engine header #include "Templates/SharedPointer.h" #include "Dom/JsonObject.h" +#include "IDesktopPlatform.h" #include "CoreMinimal.h" #include "Framework/Notifications/NotificationManager.h" #include "Widgets/Notifications/SNotificationList.h" @@ -14,7 +15,8 @@ class HOTPATCHEREDITOR_API UFlibHotPatcherEditorHelper : public UBlueprintFuncti GENERATED_BODY() public: static void CreateSaveFileNotify(const FText& InMsg,const FString& InSavedFile,SNotificationItem::ECompletionState NotifyType = SNotificationItem::CS_Success); - + static TArray SaveFileDialog(const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags); + static TArray OpenFileDialog(const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags); }; diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/FExportPatchSettings.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/FExportPatchSettings.cpp index 872194ec..203f9186 100644 --- a/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/FExportPatchSettings.cpp +++ b/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/FExportPatchSettings.cpp @@ -129,11 +129,11 @@ FString FExportPatchSettings::GetCurrentVersionSavePath() const FString FExportPatchSettings::GetCombinedAdditionalCommandletArgs() const { - FString Result = FString::Printf(TEXT("%s %s"), - *FHotPatcherSettingBase::GetCombinedAdditionalCommandletArgs(), - *UFlibPatchParserHelper::GetTargetPlatformsCmdLine(GetPakTargetPlatforms()) - ); - return Result; + return UFlibPatchParserHelper::MergeOptionsAsCmdline(TArray{ + FHotPatcherSettingBase::GetCombinedAdditionalCommandletArgs(), + UFlibPatchParserHelper::GetTargetPlatformsCmdLine(GetPakTargetPlatforms()), + IsEnableProfiling() ? TEXT("-trace=cpu,loadtimetrace") : TEXT("") + }); } FString FExportPatchSettings::GetStorageCookedDir() const diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/HotPatcherSettingBase.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/HotPatcherSettingBase.cpp index 4bd5ae83..9ce4abee 100644 --- a/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/HotPatcherSettingBase.cpp +++ b/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/HotPatcherSettingBase.cpp @@ -3,6 +3,8 @@ #include "FPlatformExternFiles.h" #include "HotPatcherLog.h" +#include "Misc/EngineVersionComparison.h" + FHotPatcherSettingBase::FHotPatcherSettingBase() { GetAssetScanConfigRef().bAnalysisFilterDependencies = true; @@ -101,24 +103,18 @@ FString FHotPatcherSettingBase::GetSaveAbsPath()const return TEXT(""); } - FString FHotPatcherSettingBase::GetCombinedAdditionalCommandletArgs() const { FString Result; - - // for(const auto& Option:GetAdditionalCommandletArgs()) - // { - // Result+=FString::Printf(TEXT("%s "),*Option); - // } - TArray Options = GetAdditionalCommandletArgs(); +#if UE_VERSION_OLDER_THAN(5,0,0) Options.AddUnique(TEXT("-NoPostLoadCacheDDC")); +#endif Result = UFlibPatchParserHelper::MergeOptionsAsCmdline(Options); return Result; } TArray FHotPatcherSettingBase::GetAllSkipContents() const - { TArray AllSkipContents;; AllSkipContents.Append(UFlibAssetManageHelper::DirectoriesToStrings(GetForceSkipContentRules())); diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/FlibAssetManageHelper.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/FlibAssetManageHelper.cpp index 2a800352..c01f42e9 100644 --- a/HotPatcher/Source/HotPatcherRuntime/Private/FlibAssetManageHelper.cpp +++ b/HotPatcher/Source/HotPatcherRuntime/Private/FlibAssetManageHelper.cpp @@ -82,6 +82,7 @@ bool UFlibAssetManageHelper::FilenameToPackagePath(const FString& InAbsPath, FSt void UFlibAssetManageHelper::UpdateAssetMangerDatabase(bool bForceRefresh) { + SCOPED_NAMED_EVENT_TEXT("UpdateAssetMangerDatabase",FColor::Red); #if WITH_EDITOR UAssetManager& AssetManager = UAssetManager::Get(); AssetManager.UpdateManagementDatabase(bForceRefresh); @@ -843,11 +844,12 @@ bool UFlibAssetManageHelper::MakePakCommandFromAssetDependencies( // TArray resault; TArray Keys; InAssetDependencies.AssetsDependenciesMap.GetKeys(Keys); - - for (const auto& Key : Keys) + FCriticalSection LocalSynchronizationObject; + ParallelFor(Keys.Num(),[&](int32 index) { + const FString& Key = Keys[index]; if (Key.Equals(TEXT("Script"))) - continue; + return; TArray AssetList; InAssetDependencies.AssetsDependenciesMap.Find(Key)->AssetDependencyDetails.GetKeys(AssetList); for (const auto& AssetLongPackageName : AssetList) @@ -855,10 +857,11 @@ bool UFlibAssetManageHelper::MakePakCommandFromAssetDependencies( TArray FinalCookedCommand; if (UFlibAssetManageHelper::MakePakCommandFromLongPackageName(InProjectDir, OverrideCookedDir,InPlatformName, AssetLongPackageName, /*InCookParams, */FinalCookedCommand,InReceivePakCommand,IsIoStoreAsset)) { + FScopeLock Lock(&LocalSynchronizationObject); OutCookCommand.Append(FinalCookedCommand); } } - } + },GForceSingleThread); return true; } diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/FExportPatchSettings.h b/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/FExportPatchSettings.h index 4247a43d..7faded44 100644 --- a/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/FExportPatchSettings.h +++ b/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/FExportPatchSettings.h @@ -98,12 +98,12 @@ struct HOTPATCHERRUNTIME_API FExportPatchSettings:public FHotPatcherSettingBase FORCEINLINE bool IsCustomPakNameRegular()const {return bCustomPakNameRegular;} FORCEINLINE FString GetPakNameRegular()const { return PakNameRegular;} + FORCEINLINE bool IsCustomPakSaveDirRegular()const {return bCustomPakSaveDirRegular;} + FORCEINLINE FString GetPakSaveDirRegular()const { return PakSaveDirRegular;} FORCEINLINE bool IsCookPatchAssets()const {return bCookPatchAssets;} FORCEINLINE bool IsIgnoreDeletedAssetsInfo()const {return bIgnoreDeletedAssetsInfo;} FORCEINLINE bool IsSaveDeletedAssetsToNewReleaseJson()const {return bStorageDeletedAssetsToNewReleaseJson;} - - FORCEINLINE FIoStoreSettings GetIoStoreSettings()const { return IoStoreSettings; } FORCEINLINE FUnrealPakSettings GetUnrealPakSettings()const {return UnrealPakSettings;} FORCEINLINE TArray GetDefaultPakListOptions()const {return DefaultPakListOptions;} @@ -225,8 +225,13 @@ struct HOTPATCHERRUNTIME_API FExportPatchSettings:public FHotPatcherSettingBase UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options") bool bCustomPakNameRegular = false; // Can use value: {VERSION} {BASEVERSION} {CHUNKNAME} {PLATFORM} - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options",meta=(EditCondition = "bCustomPakNameRegular")) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options|Regular",meta=(EditCondition = "bCustomPakNameRegular")) FString PakNameRegular = TEXT("{VERSION}_{CHUNKNAME}_{PLATFORM}_001_P"); + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options|Regular") + bool bCustomPakSaveDirRegular = false; + // Can use value: {VERSION} {BASEVERSION} {CHUNKNAME} {PLATFORM} + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options",meta=(EditCondition = "bCustomPakSaveDirRegular")) + FString PakSaveDirRegular = TEXT("{CHUNKNAME}/{PLATFORM}"); UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SaveTo") bool bStorageNewRelease = true; diff --git a/Mods/HotChunker b/Mods/HotChunker index cf6424b3..8f9a005a 160000 --- a/Mods/HotChunker +++ b/Mods/HotChunker @@ -1 +1 @@ -Subproject commit cf6424b35ebdfcd248341d4187fff444eaa75d96 +Subproject commit 8f9a005ae497deac32e02e0cd56d51c180a9c2d4 From bcdde4d4ed12147fcc917463fb3cd799496450c7 Mon Sep 17 00:00:00 2001 From: hxhb Date: Tue, 13 Dec 2022 11:14:58 +0000 Subject: [PATCH 08/81] optimize impl --- .../Classes/Commandlets/CommandletHelper.cpp | 4 ++- .../Private/FlibHotPatcherCoreHelper.cpp | 20 +++++++------- .../Private/FlibPatchParserHelper.cpp | 26 +++++++++++++------ .../Public/FlibPatchParserHelper.h | 1 + Mods/HotChunker | 2 +- 5 files changed, 32 insertions(+), 21 deletions(-) diff --git a/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/CommandletHelper.cpp b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/CommandletHelper.cpp index 481a429b..e2140761 100644 --- a/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/CommandletHelper.cpp +++ b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/CommandletHelper.cpp @@ -88,7 +88,9 @@ void CommandletHelper::MainTick(TFunction IsRequestExit) // main loop FDateTime LastConnectionTime = FDateTime::UtcNow(); - while (GIsRunning && !IsRequestingExit() && !IsRequestExit()) + while (GIsRunning && + // !IsRequestingExit() && + !IsRequestExit()) { GEngine->UpdateTimeAndHandleMaxTickRate(); GEngine->Tick(FApp::GetDeltaTime(), false); diff --git a/HotPatcher/Source/HotPatcherCore/Private/FlibHotPatcherCoreHelper.cpp b/HotPatcher/Source/HotPatcherCore/Private/FlibHotPatcherCoreHelper.cpp index b4df2797..da63966d 100644 --- a/HotPatcher/Source/HotPatcherCore/Private/FlibHotPatcherCoreHelper.cpp +++ b/HotPatcher/Source/HotPatcherCore/Private/FlibHotPatcherCoreHelper.cpp @@ -984,10 +984,15 @@ FString UFlibHotPatcherCoreHelper::ReplacePakRegular(const FReplacePakRegular& R { Result = Result.Replace(*Operator.Name,*(Operator.Do())); } - while(Result.Contains(TEXT("__"))) + auto ReplaceDoubleLambda = [](FString& Src,const FString& From,const FString& To) { - Result = Result.Replace(TEXT("__"),TEXT("_")); - } + while(Src.Contains(From)) + { + Src = Src.Replace(*From,*To); + } + }; + ReplaceDoubleLambda(Result,TEXT("__"),TEXT("_")); + ReplaceDoubleLambda(Result,TEXT("--"),TEXT("-")); return Result; }; @@ -2708,12 +2713,5 @@ void UFlibHotPatcherCoreHelper::DumpActiveTargetPlatforms() FString UFlibHotPatcherCoreHelper::GetPlatformsStr(TArray Platforms) { - FString result; - for(auto Platform:Platforms) - { - FString PlatformStr = THotPatcherTemplateHelper::GetEnumNameByValue(Platform,false); - result+=FString::Printf(TEXT("%s,"),*PlatformStr); - } - result.RemoveFromEnd(TEXT(",")); - return result; + return UFlibPatchParserHelper::GetPlatformsStr(Platforms); } \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/FlibPatchParserHelper.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/FlibPatchParserHelper.cpp index 0365d489..f1e3b598 100644 --- a/HotPatcher/Source/HotPatcherRuntime/Private/FlibPatchParserHelper.cpp +++ b/HotPatcher/Source/HotPatcherRuntime/Private/FlibPatchParserHelper.cpp @@ -1129,16 +1129,15 @@ TArray UFlibPatchParserHelper::CollectPakCommandByChunk( const FExportPatchSettings* PatcheSettings ) { - auto CollectPakCommandsByChunkLambda = [PatcheSettings](const FPatchVersionDiff& DiffInfo, const FChunkInfo& Chunk, const FString& PlatformName/*, const TArray& PakOptions*/)->TArray + TArray PakCommands; + auto CollectPakCommandsByChunkLambda = [&PakCommands,PatcheSettings](const FPatchVersionDiff& DiffInfo, const FChunkInfo& Chunk, const FString& PlatformName/*, const TArray& PakOptions*/) { ETargetPlatform Platform; THotPatcherTemplateHelper::GetEnumValueByName(PlatformName,Platform); TArray CollectPlatforms = {ETargetPlatform::AllPlatforms}; CollectPlatforms.AddUnique(Platform); FChunkAssetDescribe ChunkAssetsDescrible = UFlibPatchParserHelper::CollectFChunkAssetsDescribeByChunk(PatcheSettings, DiffInfo ,Chunk, CollectPlatforms); - - TArray PakCommands; - + bool bIoStore =false; bool bAllowBulkDataInIoStore = false; #if ENGINE_MAJOR_VERSION > 4 ||ENGINE_MINOR_VERSION > 25 @@ -1254,10 +1253,9 @@ TArray UFlibPatchParserHelper::CollectPakCommandByChunk( // NotCompressOptions.Remove(TEXT("-compress")); auto ReceivePakCommandExFilesLambda = [&PakCommands](const FPakCommand& InCommand){ PakCommands.Emplace(InCommand); }; UFlibPatchParserHelper::GetPakCommandsFromInternalInfo(ChunkAssetsDescrible.InternalFiles, PlatformName,/* NotCompressOptions,*/ ReceivePakCommandExFilesLambda); - return PakCommands; }; - - return CollectPakCommandsByChunkLambda(DiffInfo, Chunk, PlatformName/*, PakOptions*/); + CollectPakCommandsByChunkLambda(DiffInfo, Chunk, PlatformName/*, PakOptions*/); + return PakCommands; } FHotPatcherVersion UFlibPatchParserHelper::ExportReleaseVersionInfoByChunk( @@ -2198,4 +2196,16 @@ FString UFlibPatchParserHelper::MergeOptionsAsCmdline(const TArray& InO } Cmdline.RemoveFromEnd(TEXT(" ")); return Cmdline; -}; \ No newline at end of file +}; + +FString UFlibPatchParserHelper::GetPlatformsStr(TArray Platforms) +{ + FString result; + for(auto Platform:Platforms) + { + FString PlatformStr = THotPatcherTemplateHelper::GetEnumNameByValue(Platform,false); + result+=FString::Printf(TEXT("%s,"),*PlatformStr); + } + result.RemoveFromEnd(TEXT(",")); + return result; +} \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/FlibPatchParserHelper.h b/HotPatcher/Source/HotPatcherRuntime/Public/FlibPatchParserHelper.h index f97c2a7f..64ca6b3f 100644 --- a/HotPatcher/Source/HotPatcherRuntime/Public/FlibPatchParserHelper.h +++ b/HotPatcher/Source/HotPatcherRuntime/Public/FlibPatchParserHelper.h @@ -242,5 +242,6 @@ class HOTPATCHERRUNTIME_API UFlibPatchParserHelper : public UBlueprintFunctionLi static void SetPropertyTransient(UStruct* Struct,const FString& PropertyName,bool bTransient); static FString GetTargetPlatformsCmdLine(const TArray& Platforms); static FString MergeOptionsAsCmdline(const TArray& InOptions); + static FString GetPlatformsStr(TArray Platforms); }; diff --git a/Mods/HotChunker b/Mods/HotChunker index 8f9a005a..fb8285bf 160000 --- a/Mods/HotChunker +++ b/Mods/HotChunker @@ -1 +1 @@ -Subproject commit 8f9a005ae497deac32e02e0cd56d51c180a9c2d4 +Subproject commit fb8285bf432c9e27086bdf68dab037cb295c95f2 From be028257e8733d406dfa3939db4ce2065b4d7e12 Mon Sep 17 00:00:00 2001 From: hxhb Date: Wed, 14 Dec 2022 11:13:10 +0000 Subject: [PATCH 09/81] optimize impl --- .../Source/HotPatcherRuntime/Public/CreatePatch/TimeRecorder.h | 3 ++- Mods/HotChunker | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/TimeRecorder.h b/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/TimeRecorder.h index 72efcf41..85d6273d 100644 --- a/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/TimeRecorder.h +++ b/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/TimeRecorder.h @@ -1,8 +1,9 @@ #pragma once #include "CoreMinimal.h" +#include "HotPatcherLog.h" -struct TimeRecorder +struct HOTPATCHERRUNTIME_API TimeRecorder { TimeRecorder(const FString& InDisplay=TEXT(""),bool InAuto = true):Display(InDisplay),bAuto(InAuto) { diff --git a/Mods/HotChunker b/Mods/HotChunker index fb8285bf..64651473 160000 --- a/Mods/HotChunker +++ b/Mods/HotChunker @@ -1 +1 @@ -Subproject commit fb8285bf432c9e27086bdf68dab037cb295c95f2 +Subproject commit 64651473320f1027ddd199e2de96679b40e046a8 From abd8ac5b3dbef7401267f75208d94e5d915e3116 Mon Sep 17 00:00:00 2001 From: hxhb Date: Thu, 29 Dec 2022 11:29:51 +0000 Subject: [PATCH 10/81] optimize impl --- .../HotPatcherRuntime/Public/BaseTypes/FHotPatcherVersion.h | 3 ++- Mods/HotChunker | 2 +- Mods/HotMultiCooker | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FHotPatcherVersion.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FHotPatcherVersion.h index 7b6bf705..1b941165 100644 --- a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FHotPatcherVersion.h +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FHotPatcherVersion.h @@ -21,7 +21,8 @@ struct FHotPatcherVersion GENERATED_USTRUCT_BODY() public: - + FHotPatcherVersion()=default; + UPROPERTY(EditAnywhere,BlueprintReadWrite) FString VersionId; UPROPERTY(EditAnywhere, BlueprintReadWrite) diff --git a/Mods/HotChunker b/Mods/HotChunker index 64651473..1e73cd45 160000 --- a/Mods/HotChunker +++ b/Mods/HotChunker @@ -1 +1 @@ -Subproject commit 64651473320f1027ddd199e2de96679b40e046a8 +Subproject commit 1e73cd4567229e9d44b52c9d69dbfa05aef189ce diff --git a/Mods/HotMultiCooker b/Mods/HotMultiCooker index ccdd34e1..cc620c44 160000 --- a/Mods/HotMultiCooker +++ b/Mods/HotMultiCooker @@ -1 +1 @@ -Subproject commit ccdd34e12625708b8a562b2430dcde3aa44cc7f9 +Subproject commit cc620c448163b1e87a7364690e7a229ac650a704 From 8289261c90b0383b4482c08d20fb809bbda200de Mon Sep 17 00:00:00 2001 From: lipengzha Date: Thu, 29 Dec 2022 20:39:55 +0800 Subject: [PATCH 11/81] update version to v78.1 --- HotPatcher/Source/HotPatcherCore/HotPatcherCore.Build.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HotPatcher/Source/HotPatcherCore/HotPatcherCore.Build.cs b/HotPatcher/Source/HotPatcherCore/HotPatcherCore.Build.cs index 44c04de0..5c6c431e 100644 --- a/HotPatcher/Source/HotPatcherCore/HotPatcherCore.Build.cs +++ b/HotPatcher/Source/HotPatcherCore/HotPatcherCore.Build.cs @@ -191,7 +191,7 @@ public HotPatcherCore(ReadOnlyTargetRules Target) : base(Target) { "TOOL_NAME=\"HotPatcher\"", "CURRENT_VERSION_ID=78", - "CURRENT_PATCH_ID=0", + "CURRENT_PATCH_ID=1", "REMOTE_VERSION_FILE=\"https://imzlp.com/opensource/version.json\"" }); } From a04cec394ec2187003ee4f19a6ea4a32d3a28d13 Mon Sep 17 00:00:00 2001 From: lipengzha Date: Thu, 29 Dec 2022 20:45:48 +0800 Subject: [PATCH 12/81] fix compile error --- .../Source/HotPatcherEditor/Private/HotPatcherEditor.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherEditor.cpp b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherEditor.cpp index 6ef5729c..15397470 100644 --- a/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherEditor.cpp +++ b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherEditor.cpp @@ -454,9 +454,9 @@ void FHotPatcherEditorModule::OnAddToPatchSettings(const FToolMenuContext& MenuC void FHotPatcherEditorModule::OnPakPreset(FExportPatchSettings Config) { - TSharedPtr PatchSettings = MakeShareable(new FExportPatchSettings); - *PatchSettings = Config; - CookAndPakByPatchSettings(PatchSettings,PatchSettings->IsStandaloneMode()); + TSharedPtr TmpPatchSettings = MakeShareable(new FExportPatchSettings); + *TmpPatchSettings = Config; + CookAndPakByPatchSettings(TmpPatchSettings,TmpPatchSettings->IsStandaloneMode()); } #endif From 9dab120a66a0c478c29ad18a89d257ba9d9fb4dc Mon Sep 17 00:00:00 2001 From: lipengzha Date: Thu, 29 Dec 2022 20:48:31 +0800 Subject: [PATCH 13/81] update mods --- Mods/HotChunker | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mods/HotChunker b/Mods/HotChunker index 1e73cd45..5e903361 160000 --- a/Mods/HotChunker +++ b/Mods/HotChunker @@ -1 +1 @@ -Subproject commit 1e73cd4567229e9d44b52c9d69dbfa05aef189ce +Subproject commit 5e903361a39ab499647bbbaa2f6a11cdf34de172 From 996d8c112382bd61c50479e5b68bcf4f255c31c3 Mon Sep 17 00:00:00 2001 From: hxhb Date: Fri, 30 Dec 2022 02:31:48 +0000 Subject: [PATCH 14/81] fix shipping error & update version to v79.0 --- HotPatcher/Source/HotPatcherCore/HotPatcherCore.Build.cs | 4 ++-- .../HotPatcherRuntime/Private/FlibAssetManageHelper.cpp | 4 +++- Mods/HotChunker | 2 +- Mods/HotMultiCooker | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/HotPatcher/Source/HotPatcherCore/HotPatcherCore.Build.cs b/HotPatcher/Source/HotPatcherCore/HotPatcherCore.Build.cs index 5c6c431e..edfe51f9 100644 --- a/HotPatcher/Source/HotPatcherCore/HotPatcherCore.Build.cs +++ b/HotPatcher/Source/HotPatcherCore/HotPatcherCore.Build.cs @@ -190,8 +190,8 @@ public HotPatcherCore(ReadOnlyTargetRules Target) : base(Target) PublicDefinitions.AddRange(new string[] { "TOOL_NAME=\"HotPatcher\"", - "CURRENT_VERSION_ID=78", - "CURRENT_PATCH_ID=1", + "CURRENT_VERSION_ID=79", + "CURRENT_PATCH_ID=0", "REMOTE_VERSION_FILE=\"https://imzlp.com/opensource/version.json\"" }); } diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/FlibAssetManageHelper.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/FlibAssetManageHelper.cpp index c01f42e9..034c297c 100644 --- a/HotPatcher/Source/HotPatcherRuntime/Private/FlibAssetManageHelper.cpp +++ b/HotPatcher/Source/HotPatcherRuntime/Private/FlibAssetManageHelper.cpp @@ -715,7 +715,8 @@ SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::GetAllInValidAssetInProject",FC } } } - +#pragma warning(push) +#pragma warning(disable:4172) FAssetPackageData* UFlibAssetManageHelper::GetPackageDataByPackageName(const FString& InPackageName) { FAssetPackageData* AssetPackageData = nullptr; @@ -746,6 +747,7 @@ FAssetPackageData* UFlibAssetManageHelper::GetPackageDataByPackageName(const FSt return NULL; } +#pragma warning(pop) bool UFlibAssetManageHelper::ConvLongPackageNameToCookedPath( const FString& InProjectAbsDir, diff --git a/Mods/HotChunker b/Mods/HotChunker index 5e903361..c4cecb36 160000 --- a/Mods/HotChunker +++ b/Mods/HotChunker @@ -1 +1 @@ -Subproject commit 5e903361a39ab499647bbbaa2f6a11cdf34de172 +Subproject commit c4cecb3659e2d3461d82c44f9519c23eae45ca38 diff --git a/Mods/HotMultiCooker b/Mods/HotMultiCooker index cc620c44..0c7ac4d2 160000 --- a/Mods/HotMultiCooker +++ b/Mods/HotMultiCooker @@ -1 +1 @@ -Subproject commit cc620c448163b1e87a7364690e7a229ac650a704 +Subproject commit 0c7ac4d27fe84d24da01eb0dcaeae369a85097d6 From 9e460d8fe502d1a8a224c6dfd8150cbd19867628 Mon Sep 17 00:00:00 2001 From: hxhb Date: Fri, 30 Dec 2022 12:09:24 +0000 Subject: [PATCH 15/81] split ByGameFeature as GameFeaturePacker --- .../Commandlets/HotPluginCommandlet.cpp | 78 ----- .../Classes/Commandlets/HotPluginCommandlet.h | 17 -- .../Private/GameFeature/GameFeatureProxy.cpp | 85 ------ .../FGameFeaturePackagerSettings.h | 73 ----- .../Public/GameFeature/GameFeatureProxy.h | 24 -- .../HotPatcherEditor.Build.cs | 5 +- .../Private/CreatePatch/SPatchersPage.cpp | 1 - .../GameFeature/SGameFeaturePackageWidget.cpp | 283 ------------------ .../GameFeature/SGameFeaturePackageWidget.h | 60 ---- .../Private/HotPatcherActionManager.cpp | 10 +- .../Private/HotPatcherModBaseModule.cpp | 18 ++ .../Public/HotPatcherModBaseModule.h | 17 ++ Mods/GameFeaturePacker | 1 + Mods/HotChunker | 2 +- 14 files changed, 39 insertions(+), 635 deletions(-) delete mode 100644 HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotPluginCommandlet.cpp delete mode 100644 HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotPluginCommandlet.h delete mode 100644 HotPatcher/Source/HotPatcherCore/Private/GameFeature/GameFeatureProxy.cpp delete mode 100644 HotPatcher/Source/HotPatcherCore/Public/GameFeature/FGameFeaturePackagerSettings.h delete mode 100644 HotPatcher/Source/HotPatcherCore/Public/GameFeature/GameFeatureProxy.h delete mode 100644 HotPatcher/Source/HotPatcherEditor/Private/GameFeature/SGameFeaturePackageWidget.cpp delete mode 100644 HotPatcher/Source/HotPatcherEditor/Private/GameFeature/SGameFeaturePackageWidget.h create mode 100644 HotPatcher/Source/HotPatcherEditor/Private/HotPatcherModBaseModule.cpp create mode 100644 HotPatcher/Source/HotPatcherEditor/Public/HotPatcherModBaseModule.h create mode 160000 Mods/GameFeaturePacker diff --git a/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotPluginCommandlet.cpp b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotPluginCommandlet.cpp deleted file mode 100644 index 2574804f..00000000 --- a/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotPluginCommandlet.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include "HotPluginCommandlet.h" -#include "ThreadUtils/FProcWorkerThread.hpp" -#include "GameFeature/FGameFeaturePackagerSettings.h" -#include "FlibPatchParserHelper.h" - -#include "CommandletHelper.h" - -// engine header -#include "CoreMinimal.h" -#include "GameFeature/GameFeatureProxy.h" -#include "Misc/FileHelper.h" -#include "Misc/CommandLine.h" -#include "Kismet/KismetSystemLibrary.h" -#include "Misc/Paths.h" - -DEFINE_LOG_CATEGORY(LogHotPluginCommandlet); - -TSharedPtr PluginProc; - -int32 UHotPluginCommandlet::Main(const FString& Params) -{ - Super::Main(Params); - UE_LOG(LogHotPluginCommandlet, Display, TEXT("UHotPluginCommandlet::Main")); - - FString config_path; - bool bStatus = FParse::Value(*Params, *FString(PATCHER_CONFIG_PARAM_NAME).ToLower(), config_path); - if (!bStatus) - { - UE_LOG(LogHotPluginCommandlet, Error, TEXT("not -config=xxxx.json params.")); - return -1; - } - - if (!FPaths::FileExists(config_path)) - { - UE_LOG(LogHotPluginCommandlet, Error, TEXT("cofnig file %s not exists."), *config_path); - return -1; - } - - FString JsonContent; - bool bExportStatus = false; - if (FFileHelper::LoadFileToString(JsonContent, *config_path)) - { - - if(IsRunningCommandlet()) - { - FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); - AssetRegistryModule.Get().SearchAllAssets(true); - } - - TSharedPtr PluginPackagerSetting = MakeShareable(new FGameFeaturePackagerSettings); - THotPatcherTemplateHelper::TDeserializeJsonStringAsStruct(JsonContent,*PluginPackagerSetting); - - TMap KeyValues = THotPatcherTemplateHelper::GetCommandLineParamsMap(Params); - THotPatcherTemplateHelper::ReplaceProperty(*PluginPackagerSetting, KeyValues); - TArray AddPlatforms = CommandletHelper::ParserPlatforms(Params,ADD_PATCH_PLATFORMS); - - FString FinalConfig; - THotPatcherTemplateHelper::TSerializeStructAsJsonString(*PluginPackagerSetting,FinalConfig); - UE_LOG(LogHotPluginCommandlet, Display, TEXT("%s"), *FinalConfig); - - - UGameFeatureProxy* GameFeatureProxy = NewObject(); - GameFeatureProxy->AddToRoot(); - GameFeatureProxy->Init(PluginPackagerSetting.Get()); - GameFeatureProxy->OnPaking.AddStatic(&::CommandletHelper::ReceiveMsg); - GameFeatureProxy->OnShowMsg.AddStatic(&::CommandletHelper::ReceiveShowMsg); - bExportStatus = GameFeatureProxy->DoExport(); - - UE_LOG(LogHotPluginCommandlet,Display,TEXT("Generate Game Feature Misstion is %s!"),bExportStatus?TEXT("Successed"):TEXT("Failure")); - } - - if(FParse::Param(FCommandLine::Get(), TEXT("wait"))) - { - system("pause"); - } - - return 0; -} diff --git a/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotPluginCommandlet.h b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotPluginCommandlet.h deleted file mode 100644 index 60a7dda3..00000000 --- a/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotPluginCommandlet.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "HotPatcherCommandletBase.h" -#include "Commandlets/Commandlet.h" -#include "HotPluginCommandlet.generated.h" - -DECLARE_LOG_CATEGORY_EXTERN(LogHotPluginCommandlet, Log, All); - -UCLASS() -class UHotPluginCommandlet :public UHotPatcherCommandletBase -{ - GENERATED_BODY() - -public: - - virtual int32 Main(const FString& Params)override; -}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Private/GameFeature/GameFeatureProxy.cpp b/HotPatcher/Source/HotPatcherCore/Private/GameFeature/GameFeatureProxy.cpp deleted file mode 100644 index 890cf145..00000000 --- a/HotPatcher/Source/HotPatcherCore/Private/GameFeature/GameFeatureProxy.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "GameFeature/GameFeatureProxy.h" - -#include - - -#include "CreatePatch/PatcherProxy.h" - -bool UGameFeatureProxy::DoExport() -{ - PatchSettings = MakeShareable(new FExportPatchSettings); - for(const auto& FeatureName:GetSettingObject()->FeatureNames) - { - if(GetSettingObject()->IsSaveConfig()) - { - FString SaveToFile = FPaths::Combine(GetSettingObject()->GetSaveAbsPath(),FString::Printf(TEXT("%s_GameFeatureConfig.json"),*FeatureName)); - FString SerializedJsonStr; - THotPatcherTemplateHelper::TSerializeStructAsJsonString(*GetSettingObject(),SerializedJsonStr); - FFileHelper::SaveStringToFile(SerializedJsonStr, *SaveToFile); - } - - // make patch setting - { - PatchSettings->bByBaseVersion = false; - PatchSettings->VersionId = FeatureName; - FDirectoryPath FeaturePluginPath; - FeaturePluginPath.Path = FString::Printf(TEXT("/%s"),*PatchSettings->VersionId); - - PatchSettings->GetAssetScanConfigRef().AssetIncludeFilters.Add(FeaturePluginPath); - - FPlatformExternAssets PlatformExternAssets; - { - PlatformExternAssets.TargetPlatform = ETargetPlatform::AllPlatforms; - FExternFileInfo FeaturePlugin; - - if(UFlibPatchParserHelper::GetPluginPakPathByName(PatchSettings->VersionId,FeaturePlugin.FilePath.FilePath,FeaturePlugin.MountPath)) - { - FeaturePlugin.Type = EPatchAssetType::NEW; - PlatformExternAssets.AddExternFileToPak.Add(FeaturePlugin); - - TSharedPtr Plugin = IPluginManager::Get().FindPlugin(FeatureName); - if(Plugin) - { - for(const auto& NonContentDir:GetSettingObject()->NonContentDirs) - { - FString PluginConfigDir = FPaths::ConvertRelativePathToFull(FPaths::Combine(Plugin->GetBaseDir(),NonContentDir)); - if(FPaths::DirectoryExists(PluginConfigDir)) - { - FExternDirectoryInfo ConfigDir; - ConfigDir.DirectoryPath.Path = PluginConfigDir; - ConfigDir.MountPoint = FPaths::Combine(Plugin->GetBaseDir(),NonContentDir); - PlatformExternAssets.AddExternDirectoryToPak.Add(ConfigDir); - } - } - } - } - } - PatchSettings->AddExternAssetsToPlatform.Add(PlatformExternAssets); - PatchSettings->bCookPatchAssets = GetSettingObject()->bCookPatchAssets; - - { - PatchSettings->SerializeAssetRegistryOptions = GetSettingObject()->SerializeAssetRegistryOptions; - PatchSettings->SerializeAssetRegistryOptions.AssetRegistryMountPointRegular = FString::Printf(TEXT("%s[%s]"),AS_PLUGINDIR_MARK,*FeatureName); - PatchSettings->SerializeAssetRegistryOptions.AssetRegistryNameRegular = FString::Printf(TEXT("AssetRegistry.bin")); - } - { - PatchSettings->CookShaderOptions = GetSettingObject()->CookShaderOptions; - PatchSettings->CookShaderOptions.bSharedShaderLibrary = true; - PatchSettings->CookShaderOptions.bNativeShader = true; - PatchSettings->CookShaderOptions.ShaderLibMountPointRegular = FString::Printf(TEXT("%s[%s]"),AS_PLUGINDIR_MARK,*FeatureName); - } - PatchSettings->IoStoreSettings = GetSettingObject()->IoStoreSettings; - PatchSettings->EncryptSettings = GetSettingObject()->EncryptSettings; - PatchSettings->PakTargetPlatforms.Append(GetSettingObject()->TargetPlatforms); - PatchSettings->SavePath.Path = GetSettingObject()->GetSaveAbsPath(); - PatchSettings->bStorageNewRelease = false; - PatchSettings->bStorageConfig = true; - } - PatcherProxy = NewObject(); - PatcherProxy->AddToRoot(); - PatcherProxy->Init(PatchSettings.Get()); - PatcherProxy->DoExport(); - - } - return Super::DoExport(); -} diff --git a/HotPatcher/Source/HotPatcherCore/Public/GameFeature/FGameFeaturePackagerSettings.h b/HotPatcher/Source/HotPatcherCore/Public/GameFeature/FGameFeaturePackagerSettings.h deleted file mode 100644 index ea20f025..00000000 --- a/HotPatcher/Source/HotPatcherCore/Public/GameFeature/FGameFeaturePackagerSettings.h +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once - -#include "HotPatcherLog.h" -#include "CreatePatch/HotPatcherSettingBase.h" -#include "FlibPatchParserHelper.h" -#include "HotPatcherLog.h" -#include "CreatePatch/FExportPatchSettings.h" - -// engine header -#include "Misc/FileHelper.h" -#include "CoreMinimal.h" -#include "ETargetPlatform.h" -#include "UObject/ObjectMacros.h" -#include "UObject/Object.h" -#include "Engine/EngineTypes.h" -#include "Kismet/KismetStringLibrary.h" -#include "Serialization/JsonSerializer.h" -#include "Serialization/JsonWriter.h" - -#include "FGameFeaturePackagerSettings.generated.h" - -/** Singleton wrapper to allow for using the setting structure in SSettingsView */ -USTRUCT(BlueprintType) -struct HOTPATCHERCORE_API FGameFeaturePackagerSettings:public FHotPatcherSettingBase -{ - GENERATED_USTRUCT_BODY() -public: - FGameFeaturePackagerSettings() - { - SerializeAssetRegistryOptions.bSerializeAssetRegistry = true; - NonContentDirs.AddUnique(TEXT("Config")); - } - virtual ~FGameFeaturePackagerSettings(){}; - - FORCEINLINE static FGameFeaturePackagerSettings* Get() - { - static FGameFeaturePackagerSettings StaticIns; - - return &StaticIns; - } - - UPROPERTY(EditAnywhere) - TArray FeatureNames; - - UPROPERTY(EditAnywhere, BlueprintReadWrite) - bool bAutoLoadFeaturePlugin = true; - // Config/ Script/ etc. - UPROPERTY(EditAnywhere, BlueprintReadWrite) - TArray NonContentDirs; - /* - * Cook Asset in current patch - * shader code gets saved inline inside material assets - * bShareMaterialShaderCode as false - */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Base") - bool bCookPatchAssets = true; - - // UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Base", meta=(EditCondition = "bCookPatchAssets")) - FCookShaderOptions CookShaderOptions; - // UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Base", meta=(EditCondition = "bCookPatchAssets")) - FAssetRegistryOptions SerializeAssetRegistryOptions; - - // support UE4.26 later - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Base") - FIoStoreSettings IoStoreSettings; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Base") - FPakEncryptSettings EncryptSettings; - - UPROPERTY(EditAnywhere, Category="Base") - TArray TargetPlatforms; -}; - diff --git a/HotPatcher/Source/HotPatcherCore/Public/GameFeature/GameFeatureProxy.h b/HotPatcher/Source/HotPatcherCore/Public/GameFeature/GameFeatureProxy.h deleted file mode 100644 index 9ef18b37..00000000 --- a/HotPatcher/Source/HotPatcherCore/Public/GameFeature/GameFeatureProxy.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once -#include "ThreadUtils/FThreadUtils.hpp" -#include "FGameFeaturePackagerSettings.h" -// engine header -#include "Templates/SharedPointer.h" -#include "CreatePatch/HotPatcherProxyBase.h" -#include "GameFeatureProxy.generated.h" - - -UCLASS() -class HOTPATCHERCORE_API UGameFeatureProxy:public UHotPatcherProxyBase -{ - GENERATED_BODY() -public: - virtual bool DoExport() override; - virtual FGameFeaturePackagerSettings* GetSettingObject()override{ return (FGameFeaturePackagerSettings*)Setting; } - -protected: - UPROPERTY() - UPatcherProxy* PatcherProxy; -private: - TSharedPtr PatchSettings; - TSharedPtr ThreadWorker; -}; diff --git a/HotPatcher/Source/HotPatcherEditor/HotPatcherEditor.Build.cs b/HotPatcher/Source/HotPatcherEditor/HotPatcherEditor.Build.cs index c7e58e07..da0fe5b1 100644 --- a/HotPatcher/Source/HotPatcherEditor/HotPatcherEditor.Build.cs +++ b/HotPatcher/Source/HotPatcherEditor/HotPatcherEditor.Build.cs @@ -81,10 +81,7 @@ public HotPatcherEditor(ReadOnlyTargetRules Target) : base(Target) BuildVersion Version; BuildVersion.TryRead(BuildVersion.GetDefaultFileName(), out Version); AddPublicDefinitions("WITH_EDITOR_SECTION", Version.MajorVersion > 4 || Version.MinorVersion > 24); - // Game feature - bool bEnableGameFeature = false; - AddPublicDefinitions("ENGINE_GAME_FEATURE", bEnableGameFeature || (Target.Version.MajorVersion > 4 || Target.Version.MinorVersion > 26)); - + System.Console.WriteLine("MajorVersion {0} MinorVersion: {1} PatchVersion {2}",Target.Version.MajorVersion,Target.Version.MinorVersion,Target.Version.PatchVersion); PublicDefinitions.AddRange(new string[] diff --git a/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/SPatchersPage.cpp b/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/SPatchersPage.cpp index cadf328e..3bdc3a13 100644 --- a/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/SPatchersPage.cpp +++ b/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/SPatchersPage.cpp @@ -4,7 +4,6 @@ #include "CreatePatch/SHotPatcherPatchWidget.h" #include "CreatePatch/SHotPatcherReleaseWidget.h" #include "ShaderPatch/SShaderPatchWidget.h" -#include "GameFeature/SGameFeaturePackageWidget.h" // engine header #include "HotPatcherEditor.h" diff --git a/HotPatcher/Source/HotPatcherEditor/Private/GameFeature/SGameFeaturePackageWidget.cpp b/HotPatcher/Source/HotPatcherEditor/Private/GameFeature/SGameFeaturePackageWidget.cpp deleted file mode 100644 index 329c727f..00000000 --- a/HotPatcher/Source/HotPatcherEditor/Private/GameFeature/SGameFeaturePackageWidget.cpp +++ /dev/null @@ -1,283 +0,0 @@ -#include "SGameFeaturePackageWidget.h" -#include "FlibPatchParserHelper.h" -#include "FlibHotPatcherCoreHelper.h" -#include "FlibHotPatcherEditorHelper.h" -#include "HotPatcherEditor.h" -#include "GameFeature/FGameFeaturePackagerSettings.h" -#include "CreatePatch/PatcherProxy.h" -#include "CreatePatch/SHotPatcherPatchWidget.h" -#include "GameFeature/GameFeatureProxy.h" -#include "Interfaces/IPluginManager.h" -#include "Kismet/KismetSystemLibrary.h" -#include "Kismet/KismetTextLibrary.h" -#include "Misc/EngineVersionComparison.h" - -#if !UE_VERSION_OLDER_THAN(5,1,0) - typedef FAppStyle FEditorStyle; -#endif - -#define LOCTEXT_NAMESPACE "SHotPatcherGameFeaturePackager" - -void SGameFeaturePackageWidget::Construct(const FArguments& InArgs, - TSharedPtr InCreateModel) -{ - GameFeaturePackagerSettings = MakeShareable(new FGameFeaturePackagerSettings); - CreateExportFilterListView(); - - SetContext(InCreateModel); - - ChildSlot - [ - SNew(SVerticalBox) - + SVerticalBox::Slot() - .AutoHeight() - .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .VAlign(VAlign_Center) - [ - SettingsView->GetWidget()->AsShared() - ] - ] - - + SVerticalBox::Slot() - .AutoHeight() - .Padding(0.0, 8.0, 0.0, 0.0) - + SVerticalBox::Slot() - .AutoHeight() - .HAlign(HAlign_Right) - .Padding(4, 4, 10, 4) - [ - SNew(SButton) - .Text(LOCTEXT("GenerateGameFeature", "Generate GameFeature")) - .OnClicked(this,&SGameFeaturePackageWidget::DoGameFeaturePackager) - .IsEnabled(this,&SGameFeaturePackageWidget::CanGameFeaturePackager) - .ToolTipText(this,&SGameFeaturePackageWidget::GetGenerateTooltipText) - ] - ]; -} - -void SGameFeaturePackageWidget::ImportConfig() -{ - UE_LOG(LogHotPatcher, Log, TEXT("Import Game Feature Packager Config")); - TArray Files = this->OpenFileDialog(); - if (!Files.Num()) return; - - FString LoadFile = Files[0]; - - FString JsonContent; - if (UFlibAssetManageHelper::LoadFileToString(LoadFile, JsonContent)) - { - // UFlibHotPatcherCoreHelper::DeserializeReleaseConfig(ExportReleaseSettings, JsonContent); - THotPatcherTemplateHelper::TDeserializeJsonStringAsStruct(JsonContent,*GameFeaturePackagerSettings); - SettingsView->GetDetailsView()->ForceRefresh(); - } -} - -void SGameFeaturePackageWidget::ExportConfig() const -{ - UE_LOG(LogHotPatcher, Log, TEXT("Export Game Feature Packager Config")); - TArray Files = this->SaveFileDialog(); - - if (!Files.Num()) return; - - FString SaveToFile = Files[0].EndsWith(TEXT(".json")) ? Files[0] : Files[0].Append(TEXT(".json")); - - if (GameFeaturePackagerSettings) - { - FString SerializedJsonStr; - THotPatcherTemplateHelper::TSerializeStructAsJsonString(*GameFeaturePackagerSettings,SerializedJsonStr); - if (FFileHelper::SaveStringToFile(SerializedJsonStr, *SaveToFile)) - { - FText Msg = LOCTEXT("SavedGameFeatureConfigMas", "Successd to Export the Game Feature Packager Config."); - UFlibHotPatcherEditorHelper::CreateSaveFileNotify(Msg, SaveToFile); - } - } -} - -void SGameFeaturePackageWidget::ResetConfig() -{ - UE_LOG(LogHotPatcher, Log, TEXT("Reset Game Feature Packager Config")); - FString DefaultSettingJson; - THotPatcherTemplateHelper::TSerializeStructAsJsonString(*FGameFeaturePackagerSettings::Get(),DefaultSettingJson); - THotPatcherTemplateHelper::TDeserializeJsonStringAsStruct(DefaultSettingJson,*GameFeaturePackagerSettings); - SettingsView->GetDetailsView()->ForceRefresh(); -} - -// #if ENGINE_GAME_FEATURE -// #include "GameFeaturesSubsystem.h" -// #endif -void SGameFeaturePackageWidget::DoGenerate() -{ -#if ENGINE_GAME_FEATURE - if(GetConfigSettings()->bAutoLoadFeaturePlugin) - { - // FString FeatureName = GetConfigSettings()->FeatureName; - FString OutPluginURL; - UWorld* World = GEditor->GetEditorWorldContext().World(); - - for(const auto& FeatureName:GetConfigSettings()->FeatureNames) - { - if(World) - { - auto GameFeatureFounder = [](const FString& FeatureName) - { - bool bFound = false; - FString FeaturePluginDir = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectPluginsDir(),TEXT("GameFeatures"),FeatureName)); - FString FeatureUPluginPath = FPaths::Combine(FeaturePluginDir,FString::Printf(TEXT("%s.uplugin"),*FeatureName)); - - if(FPaths::DirectoryExists(FeaturePluginDir) && FPaths::FileExists(FeatureUPluginPath)) - { - bFound = true; - } - return bFound; - }; - - if(IPluginManager::Get().FindPlugin(TEXT("GameFeatures")).IsValid() && - IPluginManager::Get().FindPlugin(TEXT("ModularGameplay")).IsValid() - ) - { - if(GameFeatureFounder(FeatureName)) - { - UKismetSystemLibrary::ExecuteConsoleCommand(World,FString::Printf(TEXT("LoadGameFeaturePlugin %s"),*FeatureName)); - UKismetSystemLibrary::ExecuteConsoleCommand(World,FString::Printf(TEXT("DeactivateGameFeaturePlugin %s"),*FeatureName)); - } - } - else - { - UE_LOG(LogHotPatcher,Warning,TEXT("GameFeatures or ModularGameplay is not Enabled!")); - } - } - } - TArray FeatureNamesTemp = GetConfigSettings()->FeatureNames; - for(const auto& FeatureName:FeatureNamesTemp) - { - if(!IPluginManager::Get().FindPlugin(FeatureName)) - { - GetConfigSettings()->FeatureNames.Remove(FeatureName); - UE_LOG(LogHotPatcher,Error,TEXT("%s load faild, %s"),*FeatureName); - } - } - FeaturePackager(); - // UGameFeaturesSubsystem::Get().GetPluginURLForBuiltInPluginByName(FeatureName,OutPluginURL); - // UGameFeaturesSubsystem::Get().LoadGameFeaturePlugin(OutPluginURL,FGameFeaturePluginDeactivateComplete::CreateLambda([this](const UE::GameFeatures::FResult& InStatus) - // { - // if(InStatus.IsValid() && !InStatus.HasError()) - // { - // FeaturePackager(); - // } - // else - // { - // UE_LOG(LogHotPatcher,Error,TEXT("Package Feature %s faild, %s"),*GetConfigSettings()->FeatureName,**InStatus.TryGetError()); - // } - // })); - } -#endif -} - -void SGameFeaturePackageWidget::FeaturePackager() -{ - if(!GetConfigSettings()->IsStandaloneMode()) - { - UGameFeatureProxy* GameFeatureProxy = NewObject(); - GameFeatureProxy->AddToRoot(); - GameFeatureProxy->Init(GetConfigSettings()); - GameFeatureProxy->DoExport(); - } - else - { - FString CurrentConfig; - THotPatcherTemplateHelper::TSerializeStructAsJsonString(*GetConfigSettings(),CurrentConfig); - FString SaveConfigTo = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectSavedDir(),TEXT("HotPatcher"),FString::Printf(TEXT("GameFeatureConfig.json")))); - FFileHelper::SaveStringToFile(CurrentConfig,*SaveConfigTo); - FString MissionCommand = FString::Printf(TEXT("\"%s\" -run=HotPlugin -config=\"%s\" %s"),*UFlibPatchParserHelper::GetProjectFilePath(),*SaveConfigTo,*GetConfigSettings()->GetCombinedAdditionalCommandletArgs()); - UE_LOG(LogHotPatcher,Log,TEXT("HotPatcher %s Mission: %s %s"),*GetMissionName(),*UFlibHotPatcherCoreHelper::GetUECmdBinary(),*MissionCommand); - FHotPatcherEditorModule::Get().RunProcMission(UFlibHotPatcherCoreHelper::GetUECmdBinary(),MissionCommand,GetMissionName()); - } -} - -FText SGameFeaturePackageWidget::GetGenerateTooltipText() const -{ - FString FinalString; - struct FStatus - { - FStatus(bool InMatch,const FString& InDisplay):bMatch(InMatch) - { - Display = FString::Printf(TEXT("%s:%s"),*InDisplay,InMatch?TEXT("true"):TEXT("false")); - } - FString GetDisplay()const{return Display;} - bool bMatch; - FString Display; - }; - TArray AllStatus; - AllStatus.Emplace(HasValidConfig(),TEXT("HasValidGameFeatureConfig")); - bool bHasSavePath = GameFeaturePackagerSettings->GetSaveAbsPath().IsEmpty()?false:FPaths::DirectoryExists(GameFeaturePackagerSettings->GetSaveAbsPath()); - AllStatus.Emplace(bHasSavePath,TEXT("HasSavePath")); - - for(const auto& Status:AllStatus) - { - FinalString+=FString::Printf(TEXT("%s\n"),*Status.GetDisplay()); - } - return UKismetTextLibrary::Conv_StringToText(FinalString); -} - -// #include "DetailsCustomization/CustomGameFeatursDetails.h" - -void SGameFeaturePackageWidget::CreateExportFilterListView() -{ - // Create a property view - FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked("PropertyEditor"); - - FDetailsViewArgs DetailsViewArgs; - { - DetailsViewArgs.bAllowSearch = true; - DetailsViewArgs.bHideSelectionTip = true; - DetailsViewArgs.bLockable = false; - DetailsViewArgs.bSearchInitialKeyFocus = true; - DetailsViewArgs.bUpdatesFromSelection = false; - DetailsViewArgs.NotifyHook = nullptr; - DetailsViewArgs.bShowOptions = true; - DetailsViewArgs.bShowModifiedPropertiesOption = false; - DetailsViewArgs.bShowScrollBar = false; - DetailsViewArgs.bShowOptions = true; - DetailsViewArgs.bUpdatesFromSelection= true; - } - - FStructureDetailsViewArgs StructureViewArgs; - { - StructureViewArgs.bShowObjects = true; - StructureViewArgs.bShowAssets = true; - StructureViewArgs.bShowClasses = true; - StructureViewArgs.bShowInterfaces = true; - } - - SettingsView = EditModule.CreateStructureDetailView(DetailsViewArgs, StructureViewArgs, nullptr); - FStructOnScope* Struct = new FStructOnScope(FGameFeaturePackagerSettings::StaticStruct(), (uint8*)GameFeaturePackagerSettings.Get()); - // SettingsView->GetOnFinishedChangingPropertiesDelegate().AddRaw(ExportReleaseSettings.Get(),&FGameFeaturePackagerSettings::OnFinishedChangingProperties); - // SettingsView->GetDetailsView()->RegisterInstancedCustomPropertyLayout(FGameFeaturePackagerSettings::StaticStruct(),FOnGetDetailCustomizationInstance::CreateStatic(&FReleaseSettingsDetails::MakeInstance)); - // SettingsView->GetDetailsView()->RegisterInstancedCustomPropertyTypeLayout(FGameFeaturePackagerSettings::StaticStruct()->GetFName(),FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FCustomGameFeaturePackagerSettingsDetails::MakeInstance)); - SettingsView->SetStructureData(MakeShareable(Struct)); -} - -bool SGameFeaturePackageWidget::CanGameFeaturePackager() const -{ - bool bHasSavePath = GameFeaturePackagerSettings->GetSaveAbsPath().IsEmpty()?false:FPaths::DirectoryExists(GameFeaturePackagerSettings->GetSaveAbsPath()); - return HasValidConfig() && bHasSavePath; -} - -bool SGameFeaturePackageWidget::HasValidConfig() const -{ - bool bHasTarget = !!GetConfigSettings()->TargetPlatforms.Num(); - return bHasTarget; -} - -FReply SGameFeaturePackageWidget::DoGameFeaturePackager() -{ - DoGenerate(); - return FReply::Handled(); -} - - - -#undef LOCTEXT_NAMESPACE diff --git a/HotPatcher/Source/HotPatcherEditor/Private/GameFeature/SGameFeaturePackageWidget.h b/HotPatcher/Source/HotPatcherEditor/Private/GameFeature/SGameFeaturePackageWidget.h deleted file mode 100644 index 8c7f94a4..00000000 --- a/HotPatcher/Source/HotPatcherEditor/Private/GameFeature/SGameFeaturePackageWidget.h +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "Model/FPatchersModeContext.h" -#include "SHotPatcherWidgetBase.h" -#include "GameFeature/FGameFeaturePackagerSettings.h" - -// engine header -#include "Templates/SharedPointer.h" -#include "IStructureDetailsView.h" - -/** - * Implements the cooked platforms panel. - */ -class SGameFeaturePackageWidget - : public SHotPatcherWidgetBase -{ -public: - - SLATE_BEGIN_ARGS(SGameFeaturePackageWidget) { } - SLATE_END_ARGS() - -public: - - /** - * Constructs the widget. - * - * @param InArgs The Slate argument list. - */ - void Construct( const FArguments& InArgs,TSharedPtr InCreateModel); - -public: - virtual void ImportConfig(); - virtual void ImportProjectConfig(){}; - virtual void ExportConfig()const; - virtual void ResetConfig(); - virtual void DoGenerate(); - - virtual FGameFeaturePackagerSettings* GetConfigSettings() override{return GameFeaturePackagerSettings.Get();}; - virtual FGameFeaturePackagerSettings* GetConfigSettings()const {return GameFeaturePackagerSettings.Get();}; - - virtual FString GetMissionName() override{return TEXT("Game Feature Packager");} - virtual FText GetGenerateTooltipText() const override; -protected: - void CreateExportFilterListView(); - bool CanGameFeaturePackager()const; - bool HasValidConfig()const; - FReply DoGameFeaturePackager(); - void FeaturePackager(); - -private: - - // TSharedPtr mCreatePatchModel; - TSharedPtr PatchSettings; - /** Settings view ui element ptr */ - TSharedPtr SettingsView; - TSharedPtr GameFeaturePackagerSettings; -}; - diff --git a/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherActionManager.cpp b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherActionManager.cpp index e3a4b635..2d171cd3 100644 --- a/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherActionManager.cpp +++ b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherActionManager.cpp @@ -4,7 +4,7 @@ #include "CreatePatch/SHotPatcherPatchWidget.h" #include "CreatePatch/SHotPatcherReleaseWidget.h" #include "ShaderPatch/SShaderPatchWidget.h" -#include "GameFeature/SGameFeaturePackageWidget.h" + #include "Cooker/OriginalCooker/SOriginalCookWidget.h" #include "Kismet/KismetTextLibrary.h" #include "SVersionUpdater/FVersionUpdaterManager.h" @@ -135,14 +135,6 @@ void FHotPatcherActionManager::SetupDefaultActions() CREATE_ACTION_WIDGET_LAMBDA(SShaderPatchWidget,TEXT("ByShaderPatch")), -1 ); -#if ENGINE_GAME_FEATURE - DefaultActions.Emplace( - TEXT("Patcher"),LOCTEXT("ByGameFeature", "ByGameFeature"), LOCTEXT("CreateGameFeatureActionHint", "Create an Game Feature Package."), FSlateIcon(),nullptr, - CREATE_ACTION_WIDGET_LAMBDA(SGameFeaturePackageWidget,TEXT("ByGameFeature")), - 0 - ); -#endif - #if ENABLE_ORIGINAL_COOKER DefaultActions.Emplace( TEXT("Cooker"),LOCTEXT("ByOriginal", "ByOriginal"),LOCTEXT("OriginalCookerActionHint", "Use single-process Cook Content(UE Default)"),FSlateIcon(),nullptr, diff --git a/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherModBaseModule.cpp b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherModBaseModule.cpp new file mode 100644 index 00000000..e555d243 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherModBaseModule.cpp @@ -0,0 +1,18 @@ +#include "HotPatcherModBaseModule.h" + +void FHotPatcherModBaseModule::StartupModule() +{ + for(const auto& ActionDesc:GetModActions()) + { + RegistedActions.Add(ActionDesc); + FHotPatcherActionManager::Get().RegisteHotPatcherAction(ActionDesc); + } +} + +void FHotPatcherModBaseModule::ShutdownModule() +{ + for(const auto& ActionDesc:RegistedActions) + { + FHotPatcherActionManager::Get().UnRegisteHotPatcherAction(ActionDesc.Category,ActionDesc.ActionName); + } +} diff --git a/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherModBaseModule.h b/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherModBaseModule.h new file mode 100644 index 00000000..a6c620b4 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherModBaseModule.h @@ -0,0 +1,17 @@ +#pragma once + +#include "CoreMinimal.h" +#include "HotPatcherActionManager.h" +#include "Modules/ModuleManager.h" + +class HOTPATCHEREDITOR_API FHotPatcherModBaseModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + virtual TArray GetModActions()const { return TArray{}; }; +protected: + TArray RegistedActions; +}; diff --git a/Mods/GameFeaturePacker b/Mods/GameFeaturePacker new file mode 160000 index 00000000..2f6983ea --- /dev/null +++ b/Mods/GameFeaturePacker @@ -0,0 +1 @@ +Subproject commit 2f6983ea29f8009897f6eb7f5a886f41859e7e3e diff --git a/Mods/HotChunker b/Mods/HotChunker index c4cecb36..3afb2f95 160000 --- a/Mods/HotChunker +++ b/Mods/HotChunker @@ -1 +1 @@ -Subproject commit c4cecb3659e2d3461d82c44f9519c23eae45ca38 +Subproject commit 3afb2f9578a1ccd8f0fcf749a9e4924c33ddaee8 From 4c4ab3f6f046be16df86f0f8b01ab7ad8ea06fa4 Mon Sep 17 00:00:00 2001 From: hxhb Date: Wed, 4 Jan 2023 09:17:04 +0000 Subject: [PATCH 16/81] add ShaderPatcher Mod --- Mods/ShaderPatcher/Resources/Icon128.png | Bin 0 -> 12699 bytes Mods/ShaderPatcher/ShaderPatcher.uplugin | 30 +++ .../Classes/HotShaderPatchCommandlet.cpp | 67 ++++++ .../Classes/HotShaderPatchCommandlet.h | 17 ++ .../ShaderPatch/FlibShaderPatchHelper.cpp | 213 +++++++++++++++++ .../Private/ShaderPatch/ShaderPatchProxy.cpp | 105 ++++++++ .../Private/ShaderPatcherEditor.cpp | 36 +++ .../Private/Slate/SShaderPatchWidget.cpp | 225 ++++++++++++++++++ .../ShaderPatch/FExportShaderPatchSettings.h | 65 +++++ .../ShaderPatch/FlibShaderPatchHelper.h | 38 +++ .../Public/ShaderPatch/ShaderPatchProxy.h | 24 ++ .../Public/ShaderPatcherEditor.h | 20 ++ .../Public/Slate/SShaderPatchWidget.h | 60 +++++ .../ShaderPatcherEditor.Build.cs | 77 ++++++ 14 files changed, 977 insertions(+) create mode 100644 Mods/ShaderPatcher/Resources/Icon128.png create mode 100644 Mods/ShaderPatcher/ShaderPatcher.uplugin create mode 100644 Mods/ShaderPatcher/Source/ShaderPatcherEditor/Classes/HotShaderPatchCommandlet.cpp create mode 100644 Mods/ShaderPatcher/Source/ShaderPatcherEditor/Classes/HotShaderPatchCommandlet.h create mode 100644 Mods/ShaderPatcher/Source/ShaderPatcherEditor/Private/ShaderPatch/FlibShaderPatchHelper.cpp create mode 100644 Mods/ShaderPatcher/Source/ShaderPatcherEditor/Private/ShaderPatch/ShaderPatchProxy.cpp create mode 100644 Mods/ShaderPatcher/Source/ShaderPatcherEditor/Private/ShaderPatcherEditor.cpp create mode 100644 Mods/ShaderPatcher/Source/ShaderPatcherEditor/Private/Slate/SShaderPatchWidget.cpp create mode 100644 Mods/ShaderPatcher/Source/ShaderPatcherEditor/Public/ShaderPatch/FExportShaderPatchSettings.h create mode 100644 Mods/ShaderPatcher/Source/ShaderPatcherEditor/Public/ShaderPatch/FlibShaderPatchHelper.h create mode 100644 Mods/ShaderPatcher/Source/ShaderPatcherEditor/Public/ShaderPatch/ShaderPatchProxy.h create mode 100644 Mods/ShaderPatcher/Source/ShaderPatcherEditor/Public/ShaderPatcherEditor.h create mode 100644 Mods/ShaderPatcher/Source/ShaderPatcherEditor/Public/Slate/SShaderPatchWidget.h create mode 100644 Mods/ShaderPatcher/Source/ShaderPatcherEditor/ShaderPatcherEditor.Build.cs diff --git a/Mods/ShaderPatcher/Resources/Icon128.png b/Mods/ShaderPatcher/Resources/Icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..1231d4aad4d0d462fb7b178eb5b30aa61a10df0b GIT binary patch literal 12699 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 a;7#~yJh_ha>A&A^fRdb=Y>l)<=>Gxy=2LS3 literal 0 HcmV?d00001 diff --git a/Mods/ShaderPatcher/ShaderPatcher.uplugin b/Mods/ShaderPatcher/ShaderPatcher.uplugin new file mode 100644 index 00000000..e2484591 --- /dev/null +++ b/Mods/ShaderPatcher/ShaderPatcher.uplugin @@ -0,0 +1,30 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "ShaderPatcher", + "Description": "", + "Category": "Other", + "CreatedBy": "lipengzha", + "CreatedByURL": "https://imzlp.com/", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "CanContainContent": true, + "IsBetaVersion": false, + "IsExperimentalVersion": false, + "Installed": false, + "Plugins": [ + { + "Name": "HotPatcher", + "Enabled": true + } + ], + "Modules": [ + { + "Name": "ShaderPatcherEditor", + "Type": "Editor", + "LoadingPhase": "Default" + } + ] +} \ No newline at end of file diff --git a/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Classes/HotShaderPatchCommandlet.cpp b/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Classes/HotShaderPatchCommandlet.cpp new file mode 100644 index 00000000..61e03547 --- /dev/null +++ b/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Classes/HotShaderPatchCommandlet.cpp @@ -0,0 +1,67 @@ +#include "HotShaderPatchCommandlet.h" +#include "ShaderPatch/FExportShaderPatchSettings.h" +#include "CommandletHelper.h" +// engine header +#include "CoreMinimal.h" +#include "Misc/FileHelper.h" +#include "Misc/CommandLine.h" +#include "Misc/Paths.h" +#include "ShaderPatch/FExportShaderPatchSettings.h" +#include "ShaderPatch/ShaderPatchProxy.h" + +DEFINE_LOG_CATEGORY(LogHotShaderPatchCommandlet); + +int32 UHotShaderPatchCommandlet::Main(const FString& Params) +{ + Super::Main(Params); + UE_LOG(LogHotShaderPatchCommandlet, Display, TEXT("UHotShaderPatchCommandlet::Main")); + + FString config_path; + bool bStatus = FParse::Value(*Params, *FString(PATCHER_CONFIG_PARAM_NAME).ToLower(), config_path); + if (!bStatus) + { + UE_LOG(LogHotShaderPatchCommandlet, Warning, TEXT("not -config=xxxx.json params.")); + return -1; + } + + if (bStatus && !FPaths::FileExists(config_path)) + { + UE_LOG(LogHotShaderPatchCommandlet, Error, TEXT("cofnig file %s not exists."), *config_path); + return -1; + } + if(IsRunningCommandlet()) + { + // load asset registry + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + AssetRegistryModule.Get().SearchAllAssets(true); + } + + TSharedPtr ExportShaderPatchSetting = MakeShareable(new FExportShaderPatchSettings); + + FString JsonContent; + if (FPaths::FileExists(config_path) && FFileHelper::LoadFileToString(JsonContent, *config_path)) + { + THotPatcherTemplateHelper::TDeserializeJsonStringAsStruct(JsonContent,*ExportShaderPatchSetting); + } + + TMap KeyValues = THotPatcherTemplateHelper::GetCommandLineParamsMap(Params); + THotPatcherTemplateHelper::ReplaceProperty(*ExportShaderPatchSetting, KeyValues); + + FString FinalConfig; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(*ExportShaderPatchSetting,FinalConfig); + UE_LOG(LogHotShaderPatchCommandlet, Display, TEXT("%s"), *FinalConfig); + + UShaderPatchProxy* ShaderPatchProxy = NewObject(); + ShaderPatchProxy->AddToRoot(); + ShaderPatchProxy->Init(ExportShaderPatchSetting.Get()); + bool bExportStatus = ShaderPatchProxy->DoExport(); + + UE_LOG(LogHotShaderPatchCommandlet,Display,TEXT("Export Shader Patch Misstion is %s!"),bExportStatus?TEXT("Successed"):TEXT("Failure")); + + if(FParse::Param(FCommandLine::Get(), TEXT("wait"))) + { + system("pause"); + } + + return bExportStatus ? 0 : -1; +} diff --git a/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Classes/HotShaderPatchCommandlet.h b/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Classes/HotShaderPatchCommandlet.h new file mode 100644 index 00000000..2ef5866e --- /dev/null +++ b/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Classes/HotShaderPatchCommandlet.h @@ -0,0 +1,17 @@ +#pragma once + +#include "HotPatcherCommandletBase.h" +#include "Commandlets/Commandlet.h" +#include "HotShaderPatchCommandlet.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogHotShaderPatchCommandlet, All, All); + +UCLASS() +class UHotShaderPatchCommandlet :public UHotPatcherCommandletBase +{ + GENERATED_BODY() + +public: + + virtual int32 Main(const FString& Params)override; +}; \ No newline at end of file diff --git a/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Private/ShaderPatch/FlibShaderPatchHelper.cpp b/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Private/ShaderPatch/FlibShaderPatchHelper.cpp new file mode 100644 index 00000000..a9cad470 --- /dev/null +++ b/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Private/ShaderPatch/FlibShaderPatchHelper.cpp @@ -0,0 +1,213 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "ShaderPatch/FlibShaderPatchHelper.h" +#include "ETargetPlatform.h" +#include "FlibHotPatcherCoreHelper.h" +#include "Misc/Paths.h" +#include "ShaderCodeLibrary.h" +#include "Settings/ProjectPackagingSettings.h" + + +bool UFlibShaderPatchHelper::CreateShaderCodePatch(TArray const& OldMetaDataDirs, FString const& NewMetaDataDir, FString const& OutDir, bool bNativeFormat,bool bDeterministicShaderCodeOrder) +{ +#if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 26 + return FShaderLibraryCooker::CreatePatchLibrary(OldMetaDataDirs,NewMetaDataDir,OutDir,bNativeFormat,bDeterministicShaderCodeOrder); +#else + #if ENGINE_MINOR_VERSION > 25 + return FShaderCodeLibrary::CreatePatchLibrary(OldMetaDataDirs,NewMetaDataDir,OutDir,bNativeFormat,bDeterministicShaderCodeOrder); + #else + #if ENGINE_MINOR_VERSION > 23 + return FShaderCodeLibrary::CreatePatchLibrary(OldMetaDataDirs,NewMetaDataDir,OutDir,bNativeFormat); + #else + return false; + #endif + #endif +#endif +} + +TArray UFlibShaderPatchHelper::ConvDirectoryPathToStr(const TArray& Dirs) +{ + TArray result; + for(const auto& Dir:Dirs) + { + FString Path = FPaths::ConvertRelativePathToFull(Dir.Path); + if(FPaths::DirectoryExists(Path)) + { + result.AddUnique(Path); + } + } + return result; +} + +FString UFlibShaderPatchHelper::GetShaderStableInfoFileNameByShaderArchiveFileName(const FString& ShaderArchiveFileName) +{ + FString ShaderInfoFileName = ShaderArchiveFileName; + ShaderInfoFileName.RemoveFromStart(TEXT("ShaderArchive-")); + ShaderInfoFileName.RemoveFromEnd(TEXT(".ushaderbytecode")); + ShaderInfoFileName = FString::Printf(TEXT("ShaderStableInfo-%s.scl.csv"),*ShaderInfoFileName); + return ShaderInfoFileName; +} + +FString UFlibShaderPatchHelper::GetShaderInfoFileNameByShaderArchiveFileName(const FString& ShaderArchiveFileName) +{ + FString ShaderInfoFileName = ShaderArchiveFileName; + ShaderInfoFileName.RemoveFromStart(TEXT("ShaderArchive-")); + ShaderInfoFileName.RemoveFromEnd(TEXT(".ushaderbytecode")); + ShaderInfoFileName = FString::Printf(TEXT("ShaderAssetInfo-%s.assetinfo.json"),*ShaderInfoFileName); + return ShaderInfoFileName; +} + + + +// void UFlibShaderPatchHelper::InitShaderCodeLibrary(const TArray& Platforms) +// { +// const UProjectPackagingSettings* const PackagingSettings = GetDefault(); +// // IsUsingShaderCodeLibrary(); +// // CurrentCookMode == ECookMode::CookByTheBookFromTheEditor || CurrentCookMode == ECookMode::CookByTheBook; +// bool const bCacheShaderLibraries = true; +// if (bCacheShaderLibraries && PackagingSettings->bShareMaterialShaderCode) +// { +// FShaderCodeLibrary::InitForCooking(PackagingSettings->bSharedMaterialNativeLibraries); +// +// bool bAllPlatformsNeedStableKeys = false; +// // support setting without Hungarian prefix for the compatibility, but allow newer one to override +// GConfig->GetBool(TEXT("DevOptions.Shaders"), TEXT("NeedsShaderStableKeys"), bAllPlatformsNeedStableKeys, GEngineIni); +// GConfig->GetBool(TEXT("DevOptions.Shaders"), TEXT("bNeedsShaderStableKeys"), bAllPlatformsNeedStableKeys, GEngineIni); +// +// for (const ITargetPlatform* TargetPlatform : UFlibHotPatcherCoreHelper::GetTargetPlatformsByNames(Platforms)) +// { +// // Find out if this platform requires stable shader keys, by reading the platform setting file. +// bool bNeedShaderStableKeys = bAllPlatformsNeedStableKeys; +// FConfigFile PlatformIniFile; +// FConfigCacheIni::LoadLocalIniFile(PlatformIniFile, TEXT("Engine"), true, *TargetPlatform->IniPlatformName()); +// PlatformIniFile.GetBool(TEXT("DevOptions.Shaders"), TEXT("NeedsShaderStableKeys"), bNeedShaderStableKeys); +// PlatformIniFile.GetBool(TEXT("DevOptions.Shaders"), TEXT("bNeedsShaderStableKeys"), bNeedShaderStableKeys); +// +// bool bNeedsDeterministicOrder = PackagingSettings->bDeterministicShaderCodeOrder; +// FConfigFile PlatformGameIniFile; +// FConfigCacheIni::LoadLocalIniFile(PlatformGameIniFile, TEXT("Game"), true, *TargetPlatform->IniPlatformName()); +// PlatformGameIniFile.GetBool(TEXT("/Script/UnrealEd.ProjectPackagingSettings"), TEXT("bDeterministicShaderCodeOrder"), bNeedsDeterministicOrder); +// +// TArray ShaderFormats; +// TargetPlatform->GetAllTargetedShaderFormats(ShaderFormats); +// TArray ShaderFormatsWithStableKeys; +// for (FName& Format : ShaderFormats) +// { +// FShaderCodeLibrary::FShaderFormatDescriptor NewDesc; +// NewDesc.ShaderFormat = Format; +// NewDesc.bNeedsStableKeys = bNeedShaderStableKeys; +// NewDesc.bNeedsDeterministicOrder = bNeedsDeterministicOrder; +// ShaderFormatsWithStableKeys.Push(NewDesc); +// } +// +// if (ShaderFormats.Num() > 0) +// { +// FShaderCodeLibrary::CookShaderFormats(ShaderFormatsWithStableKeys); +// } +// } +// } +// } + + +// void UFlibShaderPatchHelper::CleanShaderCodeLibraries(const TArray& Platforms) +// { +// const UProjectPackagingSettings* const PackagingSettings = GetDefault(); +// bool const bCacheShaderLibraries = true;// IsUsingShaderCodeLibrary(); +// ITargetPlatformManagerModule& TPM = GetTargetPlatformManagerRef(); +// bool bIterativeCook = true;// IsCookFlagSet(ECookInitializationFlags::Iterative) || PackageDatas->GetNumCooked() != 0; +// +// // If not iterative then clean up our temporary files +// if (bCacheShaderLibraries && PackagingSettings->bShareMaterialShaderCode && !bIterativeCook) +// { +// for (const ITargetPlatform* TargetPlatform : UFlibHotPatcherCoreHelper::GetTargetPlatformsByNames(Platforms)) +// { +// TArray ShaderFormats; +// TargetPlatform->GetAllTargetedShaderFormats(ShaderFormats); +// if (ShaderFormats.Num() > 0) +// { +// FShaderCodeLibrary::CleanDirectories(ShaderFormats); +// } +// } +// } +// } +// +// FString ConvertToFullSandboxPath( const FString &FileName, bool bForWrite, const FString& PlatformName ) +// { +// FString Result = FPaths::ConvertRelativePathToFull(FileName);//ConvertToFullSandboxPath( FileName, bForWrite ); +// Result.ReplaceInline(TEXT("[Platform]"), *PlatformName); +// return Result; +// } +// +// static FString GenerateShaderCodeLibraryName(FString const& Name, bool bIsIterateSharedBuild) +// { +// FString ActualName = (!bIsIterateSharedBuild) ? Name : Name + TEXT("_SC"); +// return ActualName; +// } + +// void UFlibShaderPatchHelper::SaveShaderLibrary(const ITargetPlatform* TargetPlatform, FString const& Name, const TArray>* ChunkAssignments) +// { +// bool bIsIterateSharedBuild = true; // IsCookFlagSet(ECookInitializationFlags::IterateSharedBuild) +// FString ActualName = GenerateShaderCodeLibraryName(Name, bIsIterateSharedBuild); +// FString BasePath = FPaths::ProjectContentDir(); +// +// FString ShaderCodeDir = ConvertToFullSandboxPath(*BasePath, true, TargetPlatform->PlatformName()); +// +// const FString RootMetaDataPath = FPaths::ProjectDir() / TEXT("Metadata") / TEXT("PipelineCaches"); +// const FString MetaDataPathSB = FPaths::ConvertRelativePathToFull(*RootMetaDataPath); +// const FString MetaDataPath = MetaDataPathSB.Replace(TEXT("[Platform]"), *TargetPlatform->PlatformName()); +// +// // note that shader formats can be shared across the target platforms +// TArray ShaderFormats; +// TargetPlatform->GetAllTargetedShaderFormats(ShaderFormats); +// if (ShaderFormats.Num() > 0) +// { +// FString TargetPlatformName = TargetPlatform->PlatformName(); +// TMap> OutSCLCSVPaths; +// TArray& PlatformSCLCSVPaths = OutSCLCSVPaths.FindOrAdd(FName(TargetPlatformName)); +// bool bStoraged = FShaderCodeLibrary::SaveShaderCode(ShaderCodeDir, MetaDataPath, ShaderFormats, PlatformSCLCSVPaths, ChunkAssignments); +// +// if (UNLIKELY(!bStoraged)) +// { +// UE_LOG(LogTemp, Error, TEXT("%s"),*FString::Printf(TEXT("Saving shared material shader code library failed for %s."), *TargetPlatformName)); +// } +// else +// { +// const UProjectPackagingSettings* const PackagingSettings = GetDefault(); +// if (PackagingSettings->bSharedMaterialNativeLibraries) +// { +// bStoraged = FShaderCodeLibrary::PackageNativeShaderLibrary(ShaderCodeDir, ShaderFormats); +// if (!bStoraged) +// { +// // This is fatal - In this case we should cancel any launch on device operation or package write but we don't want to assert and crash the editor +// UE_LOG(LogTemp, Error, TEXT("%s"),*FString::Printf(TEXT("Package Native Shader Library failed for %s."), *TargetPlatformName)); +// } +// } +// for (const FString& Item : PlatformSCLCSVPaths) +// { +// UE_LOG(LogTemp, Display, TEXT("Saved scl.csv %s for platform %s, %d bytes"), *Item, *TargetPlatformName, +// IFileManager::Get().FileSize(*Item)); +// } +// } +// } +// } + +// void UFlibShaderPatchHelper::SaveGlobalShaderLibrary(const TArray& Platforms) +// { +// const TCHAR* GlobalShaderLibName = TEXT("Global"); +// bool bIsIterateSharedBuild = true; // IsCookFlagSet(ECookInitializationFlags::IterateSharedBuild) +// FString ActualName = GenerateShaderCodeLibraryName(GlobalShaderLibName, bIsIterateSharedBuild); +// +// const UProjectPackagingSettings* const PackagingSettings = GetDefault(); +// bool const bCacheShaderLibraries = true;//IsUsingShaderCodeLibrary(); +// if (bCacheShaderLibraries && PackagingSettings->bShareMaterialShaderCode) +// { +// // Save shader code map - cleaning directories is deliberately a separate loop here as we open the cache once per shader platform and we don't assume that they can't be shared across target platforms. +// for (const ITargetPlatform* TargetPlatform : UFlibHotPatcherCoreHelper::GetTargetPlatformsByNames(Platforms)) +// { +// SaveShaderLibrary(TargetPlatform, GlobalShaderLibName, nullptr); +// } +// +// FShaderCodeLibrary::CloseLibrary(ActualName); +// } +// } diff --git a/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Private/ShaderPatch/ShaderPatchProxy.cpp b/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Private/ShaderPatch/ShaderPatchProxy.cpp new file mode 100644 index 00000000..0b6ab3a2 --- /dev/null +++ b/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Private/ShaderPatch/ShaderPatchProxy.cpp @@ -0,0 +1,105 @@ +// Fill out your copyright notice in the Description page of Project Settings. +#include "ShaderPatch/ShaderPatchProxy.h" +#include "FlibHotPatcherCoreHelper.h" +#include "FlibShaderCodeLibraryHelper.h" +#include "ShaderPatch/FlibShaderPatchHelper.h" +#include "ShaderPatcherEditor.h" + +#define LOCTEXT_NAMESPACE "HotPatcherShaderPatchProxy" + +bool UShaderPatchProxy::DoExport() +{ + bool bStatus = false; + for(const auto& PlatformConfig:GetSettingObject()->ShaderPatchConfigs) + { + UE_LOG(LogShaderPatcherEditor,Display,TEXT("Generating Shader Patch for %s"),*THotPatcherTemplateHelper::GetEnumNameByValue(PlatformConfig.Platform)); + + FString SaveToPath = FPaths::Combine(GetSettingObject()->GetSaveAbsPath(),GetSettingObject()->VersionID,THotPatcherTemplateHelper::GetEnumNameByValue(PlatformConfig.Platform)); + bool bCreateStatus = UFlibShaderPatchHelper::CreateShaderCodePatch( + UFlibShaderPatchHelper::ConvDirectoryPathToStr(PlatformConfig.OldMetadataDir), + FPaths::ConvertRelativePathToFull(PlatformConfig.NewMetadataDir.Path), + SaveToPath, + PlatformConfig.bNativeFormat, + PlatformConfig.bDeterministicShaderCodeOrder + ); + + auto GetShaderPatchFormatLambda = [](const FString& ShaderPatchDir)->TMap> + { + TMap> FormatLibraryMap; + TArray LibraryFiles; + IFileManager::Get().FindFiles(LibraryFiles, *(ShaderPatchDir), *UFlibShaderCodeLibraryHelper::ShaderExtension); + + for (FString const& Path : LibraryFiles) + { + FString Name = FPaths::GetBaseFilename(Path); + if (Name.RemoveFromStart(TEXT("ShaderArchive-"))) + { + TArray Components; + if (Name.ParseIntoArray(Components, TEXT("-")) == 2) + { + FName Format(*Components[1]); + TSet& Libraries = FormatLibraryMap.FindOrAdd(Format); + Libraries.Add(Components[0]); + } + } + } + return FormatLibraryMap; + }; + + if(bCreateStatus) + { + TMap> ShaderFormatLibraryMap = GetShaderPatchFormatLambda(SaveToPath); + FText Msg = LOCTEXT("GeneratedShaderPatch", "Successd to Generated the Shader Patch."); + TArray FormatNames; + ShaderFormatLibraryMap.GetKeys(FormatNames); + for(const auto& FormatName:FormatNames) + { + TArray LibraryNames= ShaderFormatLibraryMap[FormatName].Array(); + for(const auto& LibrartName:LibraryNames) + { + FString OutputFilePath = UFlibShaderCodeLibraryHelper::GetCodeArchiveFilename(SaveToPath, LibrartName, FormatName); + if(FPaths::FileExists(OutputFilePath)) + { + bStatus = true; + if(!IsRunningCommandlet()) + { + FHotPatcherDelegates::Get().GetNotifyFileGenerated().Broadcast(Msg, OutputFilePath); + } + else + { + UE_LOG(LogShaderPatcherEditor,Display,TEXT("%s"),*Msg.ToString()); + } + } + else + { + UE_LOG(LogShaderPatcherEditor,Display,TEXT("ERROR: %s not found!"),*OutputFilePath); + } + } + } + } + } + + if(GetSettingObject()->bStorageConfig) + { + FString SerializedJsonStr; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(*GetSettingObject(),SerializedJsonStr); + + FString SaveToPath = FPaths::Combine(GetSettingObject()->GetSaveAbsPath(),GetSettingObject()->VersionID,GetSettingObject()->VersionID).Append(TEXT(".json")); + + if (FFileHelper::SaveStringToFile(SerializedJsonStr, *SaveToPath)) + { + FText Msg = LOCTEXT("SavedShaderPatchConfigMas", "Successd to Export the Shader Patch Config."); + if(!IsRunningCommandlet()) + { + FHotPatcherDelegates::Get().GetNotifyFileGenerated().Broadcast(Msg, SaveToPath); + } + else + { + UE_LOG(LogShaderPatcherEditor,Display,TEXT("%s"),*Msg.ToString()); + } + } + } + return bStatus; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Private/ShaderPatcherEditor.cpp b/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Private/ShaderPatcherEditor.cpp new file mode 100644 index 00000000..908fc0ed --- /dev/null +++ b/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Private/ShaderPatcherEditor.cpp @@ -0,0 +1,36 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ShaderPatcherEditor.h" +#include "Slate/SShaderPatchWidget.h" + +#define LOCTEXT_NAMESPACE "FShaderPatcherEditorModule" + +DEFINE_LOG_CATEGORY(LogShaderPatcherEditor); + +void FShaderPatcherEditorModule::StartupModule() +{ + FHotPatcherModBaseModule::StartupModule(); +} + +void FShaderPatcherEditorModule::ShutdownModule() +{ + FHotPatcherModBaseModule::ShutdownModule(); +} + +FHotPatcherModDesc FShaderPatcherEditorModule::GetModDesc() const +{ + FHotPatcherModDesc TmpModDesc; + TmpModDesc.ModName = MOD_NAME; + TmpModDesc.CurrentVersion = MOD_VERSION; + TmpModDesc.bIsBuiltInMod = IS_INTERNAL_MODE; + + TmpModDesc.ModActions.Emplace( + TEXT("Patcher"),MOD_NAME,TEXT("ByShaderPatch"), TEXT("Create an Shader code Patch form Metadata."), + CREATE_ACTION_WIDGET_LAMBDA(SShaderPatchWidget,TEXT("ByShaderPatch")),-1 + ); + return TmpModDesc; +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FShaderPatcherEditorModule, ShaderPatcherEditor) \ No newline at end of file diff --git a/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Private/Slate/SShaderPatchWidget.cpp b/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Private/Slate/SShaderPatchWidget.cpp new file mode 100644 index 00000000..01c8a5ac --- /dev/null +++ b/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Private/Slate/SShaderPatchWidget.cpp @@ -0,0 +1,225 @@ +#include "Slate/SShaderPatchWidget.h" +#include "FlibPatchParserHelper.h" +#include "FlibHotPatcherCoreHelper.h" +#include "FlibHotPatcherEditorHelper.h" +#include "HotPatcherEditor.h" +#include "ShaderPatch/FExportShaderPatchSettings.h" +#include "ShaderPatch/FlibShaderPatchHelper.h" +#include "RHI.h" +#include "Kismet/KismetTextLibrary.h" +#include "ShaderPatch/ShaderPatchProxy.h" + +#define LOCTEXT_NAMESPACE "SHotPatcherShaderPatch" + +void SShaderPatchWidget::Construct(const FArguments& InArgs, + TSharedPtr InContext) +{ + SetContext(InContext); + + ExportShaderPatchSettings = MakeShareable(new FExportShaderPatchSettings); + CreateExportFilterListView(); + + ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .VAlign(VAlign_Center) + [ + SettingsView->GetWidget()->AsShared() + ] + ] + + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0, 8.0, 0.0, 0.0) + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Right) + .Padding(4, 4, 10, 4) + [ + SNew(SButton) + .Text(LOCTEXT("GenerateShaderPatch", "Export ShaderPatch")) + .OnClicked(this,&SShaderPatchWidget::DoExportShaderPatch) + .IsEnabled(this,&SShaderPatchWidget::CanExportShaderPatch) + .ToolTipText(this,&SShaderPatchWidget::GetGenerateTooltipText) + ] + ]; +} + +void SShaderPatchWidget::ImportConfig() +{ + UE_LOG(LogHotPatcher, Log, TEXT("Import Shader Patch Config")); + TArray Files = this->OpenFileDialog(); + if (!Files.Num()) return; + + FString LoadFile = Files[0]; + + FString JsonContent; + if (UFlibAssetManageHelper::LoadFileToString(LoadFile, JsonContent)) + { + // UFlibHotPatcherCoreHelper::DeserializeReleaseConfig(ExportReleaseSettings, JsonContent); + THotPatcherTemplateHelper::TDeserializeJsonStringAsStruct(JsonContent,*ExportShaderPatchSettings); + SettingsView->GetDetailsView()->ForceRefresh(); + } +} + +void SShaderPatchWidget::ExportConfig() const +{ + UE_LOG(LogHotPatcher, Log, TEXT("Export Shader Patch Config")); + TArray Files = this->SaveFileDialog(); + + if (!Files.Num()) return; + + FString SaveToFile = Files[0].EndsWith(TEXT(".json")) ? Files[0] : Files[0].Append(TEXT(".json")); + + if (ExportShaderPatchSettings) + { + FString SerializedJsonStr; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(*ExportShaderPatchSettings,SerializedJsonStr); + if (FFileHelper::SaveStringToFile(SerializedJsonStr, *SaveToFile)) + { + FText Msg = LOCTEXT("SavedShaderPatchConfigMas", "Successd to Export the Shader Patch Config."); + UFlibHotPatcherEditorHelper::CreateSaveFileNotify(Msg, SaveToFile); + } + } +} + +void SShaderPatchWidget::ResetConfig() +{ + UE_LOG(LogHotPatcher, Log, TEXT("Reset Shader Patch Config")); + FString DefaultSettingJson; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(*FExportShaderPatchSettings::Get(),DefaultSettingJson); + THotPatcherTemplateHelper::TDeserializeJsonStringAsStruct(DefaultSettingJson,*ExportShaderPatchSettings); + SettingsView->GetDetailsView()->ForceRefresh(); +} + +void SShaderPatchWidget::DoGenerate() +{ + UShaderPatchProxy* ShaderPatchProxy = NewObject(); + ShaderPatchProxy->AddToRoot(); + ShaderPatchProxy->Init(ExportShaderPatchSettings.Get()); + if(!ShaderPatchProxy->GetSettingObject()->bStandaloneMode) + { + ShaderPatchProxy->DoExport(); + } + else + { + FString CurrentConfig; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(*GetConfigSettings(),CurrentConfig); + FString SaveConfigTo = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectSavedDir(),TEXT("HotPatcher"),TEXT("ShaderPatchConfig.json"))); + FFileHelper::SaveStringToFile(CurrentConfig,*SaveConfigTo); + FString MissionCommand = FString::Printf(TEXT("\"%s\" -run=HotShaderPatch -config=\"%s\" %s"),*UFlibPatchParserHelper::GetProjectFilePath(),*SaveConfigTo,*GetConfigSettings()->GetCombinedAdditionalCommandletArgs()); + UE_LOG(LogHotPatcher,Log,TEXT("HotPatcher %s Mission: %s %s"),*GetMissionName(),*UFlibHotPatcherCoreHelper::GetUECmdBinary(),*MissionCommand); + FHotPatcherEditorModule::Get().RunProcMission(UFlibHotPatcherCoreHelper::GetUECmdBinary(),MissionCommand,GetMissionName()); + } +} + +FText SShaderPatchWidget::GetGenerateTooltipText() const +{ + FString FinalString; + struct FStatus + { + FStatus(bool InMatch,const FString& InDisplay):bMatch(InMatch) + { + Display = FString::Printf(TEXT("%s:%s"),*InDisplay,InMatch?TEXT("true"):TEXT("false")); + } + FString GetDisplay()const{return Display;} + bool bMatch; + FString Display; + }; + TArray AllStatus; + AllStatus.Emplace(HasValidConfig(),TEXT("HasValidShaderPatchConfig")); + bool bHasSavePath = ExportShaderPatchSettings->GetSaveAbsPath().IsEmpty()?false:FPaths::DirectoryExists(ExportShaderPatchSettings->GetSaveAbsPath()); + AllStatus.Emplace(bHasSavePath,TEXT("HasSavePath")); + + for(const auto& Status:AllStatus) + { + FinalString+=FString::Printf(TEXT("%s\n"),*Status.GetDisplay()); + } + return UKismetTextLibrary::Conv_StringToText(FinalString); +} + +void SShaderPatchWidget::CreateExportFilterListView() +{ + // Create a property view + FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked("PropertyEditor"); + + FDetailsViewArgs DetailsViewArgs; + { + DetailsViewArgs.bAllowSearch = true; + DetailsViewArgs.bHideSelectionTip = true; + DetailsViewArgs.bLockable = false; + DetailsViewArgs.bSearchInitialKeyFocus = true; + DetailsViewArgs.bUpdatesFromSelection = false; + DetailsViewArgs.NotifyHook = nullptr; + DetailsViewArgs.bShowOptions = true; + DetailsViewArgs.bShowModifiedPropertiesOption = false; + DetailsViewArgs.bShowScrollBar = false; + DetailsViewArgs.bShowOptions = true; + DetailsViewArgs.bUpdatesFromSelection= true; + } + + FStructureDetailsViewArgs StructureViewArgs; + { + StructureViewArgs.bShowObjects = true; + StructureViewArgs.bShowAssets = true; + StructureViewArgs.bShowClasses = true; + StructureViewArgs.bShowInterfaces = true; + } + + SettingsView = EditModule.CreateStructureDetailView(DetailsViewArgs, StructureViewArgs, nullptr); + FStructOnScope* Struct = new FStructOnScope(FExportShaderPatchSettings::StaticStruct(), (uint8*)ExportShaderPatchSettings.Get()); + // SettingsView->GetOnFinishedChangingPropertiesDelegate().AddRaw(ExportReleaseSettings.Get(),&FExportShaderPatchSettings::OnFinishedChangingProperties); + // SettingsView->GetDetailsView()->RegisterInstancedCustomPropertyLayout(FExportShaderPatchSettings::StaticStruct(),FOnGetDetailCustomizationInstance::CreateStatic(&FReleaseSettingsDetails::MakeInstance)); + SettingsView->SetStructureData(MakeShareable(Struct)); +} + +bool SShaderPatchWidget::CanExportShaderPatch() const +{ + bool bHasSavePath = ExportShaderPatchSettings->GetSaveAbsPath().IsEmpty()?false:FPaths::DirectoryExists(ExportShaderPatchSettings->GetSaveAbsPath()); + return HasValidConfig() && bHasSavePath; +} + +bool SShaderPatchWidget::HasValidConfig() const +{ + auto HasValidDir = [](const TArray& Dirs)->bool + { + bool bresult = false; + if(!!Dirs.Num()) + { + for(const auto& Dir:Dirs) + { + if(FPaths::DirectoryExists(FPaths::ConvertRelativePathToFull(Dir.Path))) + { + bresult = true; + break; + } + } + } + return bresult; + }; + bool HasValidPatchConfig = false; + for(const auto& Platform:ExportShaderPatchSettings->ShaderPatchConfigs) + { + if(HasValidDir(Platform.OldMetadataDir) && + FPaths::DirectoryExists(Platform.NewMetadataDir.Path)) + { + HasValidPatchConfig = true; + break; + } + } + return HasValidPatchConfig; +} + +FReply SShaderPatchWidget::DoExportShaderPatch() +{ + DoGenerate(); + return FReply::Handled(); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Public/ShaderPatch/FExportShaderPatchSettings.h b/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Public/ShaderPatch/FExportShaderPatchSettings.h new file mode 100644 index 00000000..75980a91 --- /dev/null +++ b/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Public/ShaderPatch/FExportShaderPatchSettings.h @@ -0,0 +1,65 @@ +#pragma once + +#include "HotPatcherLog.h" +#include "CreatePatch/HotPatcherSettingBase.h" +#include "FlibPatchParserHelper.h" +#include "HotPatcherLog.h" +#include "ETargetPlatform.h" + +// engine header +#include "Misc/FileHelper.h" +#include "CoreMinimal.h" + +#include "UObject/ObjectMacros.h" +#include "UObject/Object.h" +#include "Engine/EngineTypes.h" +#include "Kismet/KismetStringLibrary.h" +#include "Serialization/JsonSerializer.h" +#include "Serialization/JsonWriter.h" + +#include "FExportShaderPatchSettings.generated.h" + + +USTRUCT(BlueprintType) +struct SHADERPATCHEREDITOR_API FShaderPatchConf +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + ETargetPlatform Platform = ETargetPlatform::None; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TArray OldMetadataDir; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FDirectoryPath NewMetadataDir; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bNativeFormat = false; + // since UE 4.26 (Below 4.26 this parameter has no effect) + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bDeterministicShaderCodeOrder = true; +}; + +/** Singleton wrapper to allow for using the setting structure in SSettingsView */ +USTRUCT(BlueprintType) +struct SHADERPATCHEREDITOR_API FExportShaderPatchSettings:public FHotPatcherSettingBase +{ + GENERATED_USTRUCT_BODY() +public: + FExportShaderPatchSettings(){} + virtual ~FExportShaderPatchSettings(){}; + + FORCEINLINE static FExportShaderPatchSettings* Get() + { + static FExportShaderPatchSettings StaticIns; + + return &StaticIns; + } + + UPROPERTY(EditAnywhere, Category="Version") + FString VersionID; + + UPROPERTY(EditAnywhere, Category="Config") + TArray ShaderPatchConfigs; + +}; + + diff --git a/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Public/ShaderPatch/FlibShaderPatchHelper.h b/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Public/ShaderPatch/FlibShaderPatchHelper.h new file mode 100644 index 00000000..27e18999 --- /dev/null +++ b/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Public/ShaderPatch/FlibShaderPatchHelper.h @@ -0,0 +1,38 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "Engine/EngineTypes.h" +#include "CoreMinimal.h" + +#include "ETargetPlatform.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "FlibShaderPatchHelper.generated.h" + + + +/** + * + */ +UCLASS() +class SHADERPATCHEREDITOR_API UFlibShaderPatchHelper : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() +public: + + UFUNCTION(BlueprintCallable) + static bool CreateShaderCodePatch(TArray const& OldMetaDataDirs, FString const& NewMetaDataDir, FString const& OutDir, bool bNativeFormat,bool bDeterministicShaderCodeOrder = true); + + UFUNCTION(BlueprintCallable) + static TArray ConvDirectoryPathToStr(const TArray& Dirs); + + + static FString GetShaderStableInfoFileNameByShaderArchiveFileName(const FString& ShaderArchiveFileName); + static FString GetShaderInfoFileNameByShaderArchiveFileName(const FString& ShaderArchiveFileName); + + // static void InitShaderCodeLibrary(const TArray& Platforms); + // static void CleanShaderCodeLibraries(const TArray& Platforms); + // void SaveGlobalShaderLibrary(const TArray& Platforms); + // void SaveShaderLibrary(const ITargetPlatform* TargetPlatform, FString const& Name, const TArray>* ChunkAssignments); +}; + diff --git a/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Public/ShaderPatch/ShaderPatchProxy.h b/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Public/ShaderPatch/ShaderPatchProxy.h new file mode 100644 index 00000000..506e4355 --- /dev/null +++ b/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Public/ShaderPatch/ShaderPatchProxy.h @@ -0,0 +1,24 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "CreatePatch/HotPatcherProxyBase.h" +#include "ShaderPatch/FExportShaderPatchSettings.h" +#include "ShaderPatchProxy.generated.h" + +/** + * + */ +UCLASS() +class SHADERPATCHEREDITOR_API UShaderPatchProxy : public UHotPatcherProxyBase +{ + GENERATED_BODY() +public: + virtual bool DoExport() override; + FORCEINLINE bool IsRunningCommandlet()const{return ::IsRunningCommandlet();} + FORCEINLINE virtual FExportShaderPatchSettings* GetSettingObject()override + { + return (FExportShaderPatchSettings*)Setting; + } +}; diff --git a/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Public/ShaderPatcherEditor.h b/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Public/ShaderPatcherEditor.h new file mode 100644 index 00000000..26e5348a --- /dev/null +++ b/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Public/ShaderPatcherEditor.h @@ -0,0 +1,20 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "HotPatcherActionManager.h" +#include "HotPatcherModBaseModule.h" +#include "Modules/ModuleManager.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogShaderPatcherEditor, All, All); + +class FShaderPatcherEditorModule : public FHotPatcherModBaseModule +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + virtual FHotPatcherModDesc GetModDesc()const override; +}; diff --git a/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Public/Slate/SShaderPatchWidget.h b/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Public/Slate/SShaderPatchWidget.h new file mode 100644 index 00000000..ae8bdf6e --- /dev/null +++ b/Mods/ShaderPatcher/Source/ShaderPatcherEditor/Public/Slate/SShaderPatchWidget.h @@ -0,0 +1,60 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Model/FPatchersModeContext.h" +#include "SHotPatcherWidgetBase.h" +#include "ShaderPatch/FExportShaderPatchSettings.h" + +// engine header +#include "Interfaces/ITargetPlatformManagerModule.h" +#include "Interfaces/ITargetPlatform.h" +#include "Templates/SharedPointer.h" +#include "IDetailsView.h" +#include "PropertyEditorModule.h" +#include "IStructureDetailsView.h" + +/** + * Implements the cooked platforms panel. + */ +class SShaderPatchWidget + : public SHotPatcherWidgetBase +{ +public: + + SLATE_BEGIN_ARGS(SShaderPatchWidget) { } + SLATE_END_ARGS() + +public: + + /** + * Constructs the widget. + * + * @param InArgs The Slate argument list. + */ + void Construct( const FArguments& InArgs,TSharedPtr InContext); + +public: + virtual void ImportConfig(); + virtual void ImportProjectConfig(){}; + virtual void ExportConfig()const; + virtual void ResetConfig(); + virtual void DoGenerate(); + virtual FExportShaderPatchSettings* GetConfigSettings() override{return ExportShaderPatchSettings.Get();}; + virtual FString GetMissionName() override{return TEXT("Shader Patch");} + virtual FText GetGenerateTooltipText() const override; +protected: + void CreateExportFilterListView(); + bool CanExportShaderPatch()const; + bool HasValidConfig()const; + FReply DoExportShaderPatch(); + +private: + + // TSharedPtr mCreatePatchModel; + + /** Settings view ui element ptr */ + TSharedPtr SettingsView; + TSharedPtr ExportShaderPatchSettings; +}; + diff --git a/Mods/ShaderPatcher/Source/ShaderPatcherEditor/ShaderPatcherEditor.Build.cs b/Mods/ShaderPatcher/Source/ShaderPatcherEditor/ShaderPatcherEditor.Build.cs new file mode 100644 index 00000000..9032195b --- /dev/null +++ b/Mods/ShaderPatcher/Source/ShaderPatcherEditor/ShaderPatcherEditor.Build.cs @@ -0,0 +1,77 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class ShaderPatcherEditor : ModuleRules +{ + public ShaderPatcherEditor(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "Json", + "JsonUtilities", + "Projects", + "TargetPlatform", + "DesktopPlatform", + "EditorStyle", + "HotPatcherRuntime", + "HotPatcherCore", + "HotPatcherEditor" + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + // ... add private dependencies that you statically link with here ... + } + ); + + if (Target.Version.MajorVersion > 4 || Target.Version.MinorVersion > 21) + { + PrivateDependencyModuleNames.Add("RenderCore"); + } + else + { + PrivateDependencyModuleNames.Add("ShaderCore"); + } + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + + PublicDefinitions.AddRange(new string[] + { + "MOD_NAME=TEXT(\"ShaderPatcher\")", + "MOD_VERSION=1.0", + "IS_INTERNAL_MODE=true" + }); + } +} From bd7d86bf8e4c56280855f047933b3dea82f12608 Mon Sep 17 00:00:00 2001 From: hxhb Date: Wed, 4 Jan 2023 09:17:29 +0000 Subject: [PATCH 17/81] optimize mod --- HotPatcher/Resources/wechatpay.png | Bin 0 -> 147463 bytes .../Source/CmdHandler/CmdHandler.Build.cs | 1 - .../Source/CmdHandler/Private/CmdHandler.cpp | 39 +- .../Cooker/HotAssetScannerCommandlet.cpp | 4 +- .../Cooker/HotGlobalShaderCommandlet.cpp | 4 +- .../Commandlets/HotShaderPatchCommandlet.cpp | 67 --- .../Commandlets/HotShaderPatchCommandlet.h | 17 - .../HotPatcherCore/HotPatcherCore.Build.cs | 1 - .../MultiCooker/FlibHotCookerHelper.cpp | 2 +- .../Cooker/MultiCooker/SingleCookerProxy.cpp | 2 +- .../Private/CreatePatch/PatcherProxy.cpp | 2 +- .../Private/FlibHotPatcherCoreHelper.cpp | 2 +- .../FCookShaderCollectionProxy.cpp | 2 +- .../FlibShaderCodeLibraryHelper.cpp | 6 +- .../ShaderPatch/FlibShaderPatchHelper.cpp | 217 ---------- .../Private/ShaderPatch/ShaderPatchProxy.cpp | 104 ----- .../FlibShaderCodeLibraryHelper.h | 19 + .../ShaderPatch/FExportShaderPatchSettings.h | 65 --- .../ShaderPatch/FlibShaderPatchHelper.h | 55 --- .../Public/ShaderPatch/ShaderPatchProxy.h | 24 -- .../Private/Cooker/SCookersPage.cpp | 17 +- .../Private/CreatePatch/SPatchersPage.cpp | 18 +- .../Private/HotPatcherActionManager.cpp | 134 +++++- .../Private/HotPatcherModBaseModule.cpp | 11 +- .../Private/HotPatcherStyle.cpp | 2 - .../FVersionUpdaterManager.cpp | 28 ++ .../SVersionUpdater/FVersionUpdaterManager.h | 20 +- .../SVersionUpdater/SVersionUpdaterWidget.cpp | 400 +++++++++++++++--- .../SVersionUpdater/SVersionUpdaterWidget.h | 61 ++- .../SVersionUpdater/VersionUpdaterStyle.cpp | 9 +- .../ShaderPatch/SShaderPatchWidget.cpp | 225 ---------- .../Public/HotPatcherActionManager.h | 38 +- .../Public/HotPatcherModBaseModule.h | 6 +- .../Public/ShaderPatch/SShaderPatchWidget.h | 60 --- .../Public/CreatePatch/FExportPatchSettings.h | 4 +- Mods/GameFeaturePacker | 2 +- Mods/HotChunker | 2 +- Mods/HotMultiCooker | 2 +- 38 files changed, 694 insertions(+), 978 deletions(-) create mode 100644 HotPatcher/Resources/wechatpay.png delete mode 100644 HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotShaderPatchCommandlet.cpp delete mode 100644 HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotShaderPatchCommandlet.h rename HotPatcher/Source/HotPatcherCore/Private/{ShaderPatch => ShaderLibUtils}/FCookShaderCollectionProxy.cpp (96%) rename HotPatcher/Source/HotPatcherCore/Private/{ShaderPatch => ShaderLibUtils}/FlibShaderCodeLibraryHelper.cpp (94%) delete mode 100644 HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/FlibShaderPatchHelper.cpp delete mode 100644 HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/ShaderPatchProxy.cpp rename HotPatcher/Source/HotPatcherCore/Public/{ShaderPatch => ShaderLibUtils}/FlibShaderCodeLibraryHelper.h (72%) delete mode 100644 HotPatcher/Source/HotPatcherCore/Public/ShaderPatch/FExportShaderPatchSettings.h delete mode 100644 HotPatcher/Source/HotPatcherCore/Public/ShaderPatch/FlibShaderPatchHelper.h delete mode 100644 HotPatcher/Source/HotPatcherCore/Public/ShaderPatch/ShaderPatchProxy.h delete mode 100644 HotPatcher/Source/HotPatcherEditor/Private/ShaderPatch/SShaderPatchWidget.cpp delete mode 100644 HotPatcher/Source/HotPatcherEditor/Public/ShaderPatch/SShaderPatchWidget.h diff --git a/HotPatcher/Resources/wechatpay.png b/HotPatcher/Resources/wechatpay.png new file mode 100644 index 0000000000000000000000000000000000000000..faa2a901b733af0b5bf9b1be1a191889f27d2721 GIT binary patch literal 147463 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

G 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

Nao0}Z$57ohMu{H!h$z?B)fH>sVO(XV z!=8o1#Yz|#Ar1Sw2rMwrMq4;9QI5fOaAr>8Pc{Jdfk*(zje!6E4^1iwj~wx9BN2VN zJZ{$w(lTd;h=da-!%AYsrH!bSSfsHDQcIYBrKd-E{~cZTUcl$=fTt%$jHHFd`|*eb z`&r4)>u5Bkizig0>-MeSB?MSx_iL6()H$fkr;0^UQbZ!1G^*lIapx(_34TFqG=|ss z*d3E!uSQ3D6K!Cn0FF4+iZpo|WFrCc`^BHsMm25Dwt z3|&e*A4mXTLJ`^gZDjV}7k^i4D<4A+iFT4|#q|p&&YHvj<2E1d{pI0ATKkqaY5P#+ zXnd#9&zEgTBjU~95iQlL6)k4%n&m>I=RZdheMbfU!SOLp#Ivl)E!WQN=qZLZ%k_QF zent}N{9A}sTBqoy4{&S3r*HfVqT)cORXAt6#+q(4tmPCDz}A7LACV?yeI0NsT!Y=y zR^NvYJtoM}T`WwP2A!@mA@|mw^<8^j{yqSEi!U|3d?~Tw;u>(dsP1hx=kSJwLD|jJ z%c9SyoVE^6!(Fl)nXOEK`P+yWF>@yelW)5dqJ;{YT1e5|LKk7#nW1dLooNN>wk6t1 zNN;D*Yf*#eK#x;#9DC>rNW{ODlM8y<*a5d;k0Y_L>{qVAZb8G6;2T!gt#j`_5N9D5 z^GmO{A7^_3SbPWPkQ=sJZO*Y9X7)cekVq`Cn>n=)kD4q^x@2=8&jG%5&>pN)r_)4` z?%iGY0>l7K(AnHIoMwBVPRUqmBKbed*DsbiFeaIeILN;B{j$}2(fdS_5GhyMA$$E} zFAU*~pH*Vy|GfY;{cfez!nc8Iie@D&4~wAW>FD|C;==n2KwLXHv3;7=H0l8)KvCD- z;I`%j+<&9l|19_Z2z*j1gMyLm_^tRAj#^jx9NG_0VZhG={Y#H5(out)r6$6_kynHj zh*G3|yseY`(vA3H{-a)-c;K3sD{&hme;;t7WXi2*I~No%>%V;so`m6^we81pk}>?aV`IN$;9Q)Yw*ledIP;rA z305I5=&5?~HI54O&c|+5V`(?wsJ4Lm-al8MThUSTsHqvxJL8b*4e2LP~7V6>FMd~E0nLg<2r;11h^qU#jjEWSH%suTguxn8q1+Q zgh3)n6ePkIS`~}G_lf%C)nVWFYZkcunOhaA=^KxB6!*E@1;0MGh=@`HeFZW4%Y}TIo8x}~uHt`?AOIIO)oEwFU)3G4rXUj$ z`R683f*$o~jb!k~jMJiCG7~6h&?@{lI9fUt(po_2%J&S=CP`x#@=G7&v`Ubu{ov)L zorZE1A?1Ow4ihDRg$8oQADeQ)^A}sR@~f>LY<-kK$rG9%;Lx*%Lv`(w*C`bzf~inI zfEd{A#6Rt%uXJshr0;_@_T!^)x}w!q^17>1UFjxmUMTZU|2JTb5o3@;_!zgTho(?6 zrOhH~v~C-~&@%*N!GM77jq}tTVqy2jypIXL`rJLU@T7}Ww&O_7iEu8>ZA zM}rigMY>rcGSufa0SIhV37+hSUj2j`Is+%G8uTU7A&X`hQi#(WX;mn~4?zlQbe$rJ%YJmue9!AXN_C9C? zw&~0vBAKcc15}gWu0Zowz(hKvMFRNt87O06Tn~m-Eq&nZPUUk_w3|>+pp~%&o(EnQ z2O@423&(LmiV=(Qnk~ii?>Xu*<~nR@cUW* z3TU!F7HRML8_rjs=fiPEy8s6vR~$ZsaRrs z4wby0qmillU2Dx0SwU4OCz_GXC@X*k^FElrT=UJLRDciLV>q$(m|aqX30$ZIXFV-y zmp&8y*84a~;*{`1(FyE1lH_f*nGl3AK4}=qql)gU(qv{8{{B)+PqoxyUD7@*I)B`; zq(-do7=g)u&`*~)H;wpn%S^fDFic$B#GX_8jzp^jZF6WBJts*x70b%dB*{OSS!$_b z(90m1hL^Ojg#I@~X>*vmtMmoB0?J-=B7_B^iGjl(}8xCQwv(9ZDj2t5V27OdGF}%75fk>lCq05I?ydK{>-FQBgEe@$UCIwrOhE|V`G2N z&9QGWVC#5|;&8+^dMAu(IIsq16hBN>y%3e-0vUNo?TRReZN^t|3A`6d&9o}S-WF^& zt7X(lWW--fXL)QF7XdRGt*_xO+AsErTI|t{H6;*e=)06L&b+$x=Tm$KzaZB zv%~WYy4;L4K9ePj#nEy4M}<>Q=uw_Bu{KP+&)7p5=P~w&ur`PyTqFa|XK->I#%9`c zb=^HXHb+ls?C$PvZf@@A@Bs)SpK!@!lpnOQXlcadSlSwnTBam(v=i+4&Cl7q7Wk55 zch}8!uZ#c1{=r^kaX#s%W(n6uX~sb4Z}7FC3!Ju9XVy_QDn*k5JL`?Kao+i_EG5t$EbSc7JS(iKl8vNovwiWTtHFbDv8VVYNv zwqAeoG(DZWVkyF3JYQAjYg3QP*`q4^Xq<5aO9&B$Tr$H+{R4{>TbCys$7haX*!6W+ z_Ck~roZ3Eb8M>(6a}AfOnYCgchygsGv~a$v4bc3|0$tHEnWHrzBdsa>uO9t4c|OH< zG7KOLvhL<%lwxmZ;+N_h8p8h=ZlgO=Q;@Ns>GZ%8{bs<8c4&pK+X{Em2p18ug)ArM zkkboy`W}Eo-|pDA0-{!)2aw(Bt%{@F($(_^IRWt;4ea6Vuh;2m>%k|3V7!qN^KoDn&0hquooNiPNkhk7|iNM zmf`8YWq?{a_5`zO*@Y6WmgtE4BJ^nOG!OOMnu@7=|cxqq}=LD3qUhdN_xP z)6V7osj8g5ef(yHoDC|IEegbrh_DA2wmm{v2G^YqZgGp66MWGFIc0Op*riHUQoL1P;@hW2BE z%>p3y0G!Xjx+GdevLEV?YwdUAcHFW(UaFI=+(!NV^Cx{#KZ?r-YhX9pjMzc?No$OI zaK=K7BxOg?51W8G`Ep|Sv9xrf?#LG~q;sd6zsV^2NshKi;FfYgNEZJ?d(2S}SkRSs z<C4ZfPG|wW-sh2dvH#FkBG|x(d zIvrL*1#?5F!km0s?ez3Ch)g?`&Xo0qvZ^U%Wq)BP$0f10c32(~it=-i`{U5)9XLP@ zVTx|-?DV}loB+EA%dtc-`hb6(935-x>U;~3y?Pf<(iQ0+@NoDFKp6cKIXU^8&U8Sm zSi`$^nsdNkcl)ws0zP?E&kp@d7;g$#MkiP^jH@z?;Kjvj{+D9P$$ zc`$Zvo9mw-hS5K~%5RMYWkN&JX)PQ-U(|;x!Yfcn%}TE*NI8~(Qyv=PZoZ7d*x=YL zy<%(tkLXY3uhRL8@*Rx^b=v^m*MTbd&gewai`;F7I$^V_O&xd+2RsU{$%2cfV8qE4 zN%?8D%}oFWoRolIZvvc!C=8tIU$}D# zTpeu6ze7owmJ|&euC!ICFR22_(LNaHj=&<7_QPSX?>F}^~66(0F@i$iojU^CADEIOZ()$-}ezzV7+`| z-z@VyJm-ilDL`>8-Lfl7B>`p*^;@+t>c5O)DJiATf~XJW&?>*4=gjtQYoH=l$A3TE z5Sx`LV67hj3_Wr1a4`=0{Y?uk5~Dezp7(G>Ec{?93#}07EdKz4WHerQ7&D$rz2JoO z-EIu3GQcH@kq}4@3Z7Vma2nB>B}$2=#3#ADDoai|p|^EL)c^ddx)fT~5b%v9nO7M- zWb^Zi_>s^oYzst=Ynq*9*Tg5q2NTHpV7#^gOm#3$FPixpFQ<#4go04lPbJgGRD$#! zu>5t9js|}(SW$Dpkd&ZBxY?=hO07pmrzB?-ARAD<#m(@Zp~eDvFg}bSU^}2qMJ?X} zv+H%h^vP(xk>LcDD61+G;Sjr37Qe6)fiHda>2uWyli!CIlK3(fzl!aC#K9ON@d zxp#LP)hq)(9bnOez!UgTB-!k$1eLh`mupOJUsjUkx^FoDTKT4u%W|8^?}g~pc-=1R zmMq<2Pp*o@eAosn`j^-gEy3Cdyav9p|9LAY{Q7Zo<5?>GZ^GXyom}O+DkXk|XT>6@ zND|ocT*L~xghx@Dg5{<~2~`W&$J=>aX;`g5axm_BOv-drYVv3CVg9ji>0+N&6DYx+ojH^y-^m;DU} z0?7&YrXhxpdFR+URCq;%!543$@WOQl^2I`Cf=Qu1_V>x!oRTGZ?_E_~vl``sPg90_ zT3VJI`C)M{11b4L61Xb0DJq>m>*#2 zcd*M}C9(cRC)rR&M1x1gEx1W0qokxXk(1!Z?50b8J+3gxV_~`oDXkRfk)Db+iHt)) zCvPZAzM<>DxhuAhO4=*MHq0vMd{$zA?}c6J6_-OL%2pnxsJey|ffDg!{}fUhsHTSk40zp1{y9e?hF@F|Jts}lS_ zdq+xgqY3W&%3~@aYA}0O)Jv&L){V0W+m@0PdIRo{poCYWTT;m)AUWb8f7sPm6HcM? z#8ViXRHxsgpAG~&-hKA{^WY|YpU`E|v?%dcY9-R8Y4Ua$eeelAfmHmx;6Q?2vd+}+ z`bl~T@?}18%0C>x6&_jhnA7nUMF8t(fGsw#U0h=gR6kXy=1X&*6n46E++=)CrGJ+` zs=9xWa7*`NC`)n4Pff;-Uv7*z+e}gMlYWf8A*wS%kMY9O457?*bcJyiPMdRG#3UIL znFt2xrM75L<|yj@K3p>9f2?H8H=PvBs&8s4fZI=9i8zdT<-V{Vc8+)XW>bx(CN*w> zX!fRBYm<;RuwH-rJK(*(O`>?yJ_VfPot-b7TJSH}unj&`uKpk;B?XFBHaKAv6pW5> z4S|w8XVAh8eChq)g7uYERs9M;xM6C;AH19j&1lxXrKySkemF=P%8!*&m#SEV4JVbv zD;~3KyIkC6U#}}|mp>Juvdov1*J4w0wsce<@kWcL@ZXNPk1Svhbc=_oKXk9i6p@G= zLDIgait}&^R>HuRV){s=PXE^LCAJ)Azx|8XcpQD@4-INzK~%9|`F1IaB`rpqpa*}l zafG^iukUITFW$yep32)dz|iW5xHu%H0wah4rnQx~ymiWUixOjm=6s}VSb(;ZRDDVXk^&Gzen#J2hGH}hll-E;y z_~XW?2yAVj2L$|7zSd>L@7iMD+ytyIJy!5tuw^I~op3gZ_cH%OmJy*Rpe`aj(NwCh zxtKso^{5gUz83Y1h4_u`6(l}OZ`oiQ>$_!Y48PV+J0y}K8tr@w$Ff2ai37hWgd8BU z?==c}aI$Q^^px8P^q4970`s3Uv87qT=nY1*vreJu5u+*4Aaq_W7c|%>beWRAOgmXYgR~A=%H%%6e&f1r034AW}prS<@Q&nwUl2SA}7d%}AnkaO{i|_*qyYDwr^DB`{0CQ@Q zI(yL6yn^=h=OxDum|InfLmQ+B&MI)++2Yv8sok-_22sYFF0pR9$R^ zkFV9Bh-M}K4fSSbnK#%-g3MaeunYk z8N1Z5f6|h1Sm~-L+^MZTY|^vDWCW~bp`C&&RK5|Y`yZ9xfvmV>GAH5_$a;gmBXdXr zfEs|doRyoK7OvMBiu0h!X6?EgY>w|k(b*-Dk92BU8ykaX>uYQA<3^NU!?Ud22foW; zXJrNW5-t3n^50y0l(He26XZX^DMuTZ%g64PC&ihV48gUO#-4YWp`@WnC*5K-cZc9k zXZG7+T08tjh@WkINEh}k@oe^dWfxtk@UzI! zccGey?`4)k(2I6B)$MS`BX&{?=tm)Q1Wud1M-MF}edmr^hGeHYm%?u}5E8I#^=J~F z>c2)SIXgKeypnpXj&#gaT4Q|>`pxvLoeQ(xu>zMlqsy{h_MDKSqwlJvp+wRwTwzbN zAxkBl1m21u%upXCi$8na{MC_BA@)x+gPpHxnXiU37aLfpQTfFBg_6)EDQC-5^HWt0 za=y461|W4|u4STv70i5s);tFDy}mrCNaI%AjY+lPV5JH9@+yE@?q7iCr}>C>XZs zpbm2Be6SIIyp*k;1ie1hHTB%1h1~pt0!CXa>n}V$D~}M~Y0guO%70PjD1WZO2J0m` z2hL;8#Sduj>l%oHmFOiWqQ$u7*+*<6XtVwUijKa6n_+G6iSWWJV4OT?{*PUz0zqPa za4F^j>Go0ThX%ei_eY*t(KKQCQpF{+Iln9IEi}uim6a9a_gjc46iYJAqK!(f!p2$n zBCgG`s2)w0QLj>Oz!8_@$oUp&8aiugLogOjVP#FCL|&n zXb!PLz$J}vAK=<^7~>cl9R+PrppOXFo3?rEKJfK=$u&lcZZO&VpPQ&IB^OVi8x4T! z%)z$bdG@Q#r$Asnn$yh{U4eKty<2^0J(A`QIn#L6vvCeK`MTZv1Yo&$mL>aNzrhoD z0ZaToO!IWTSMiI%G%Z6t!MTN7t&RQHKjIQE|;d!k7`70PlL;_xeO^JoY z;B_}Rxq+S(fbw`bOE8vT`Z=_3fJGZXYD@#kd1^^Xwc*0?fZ8%>?OIfDPy>pM#*fhg z1Y{ABCxDxTLpb#0B7l}+(9E1G_xA0q@Q>Pqv80lf*D}~#f5$b;2<%$-6o-B( zDn-pM3MnN_;c0bx)NeTpkR+rrs1*mnK8R=IYX=*#uR6CI)~x^=5@=JX(wem5K4#Ev zSk21J1ORcrhf8z1y$}ztFUI3o9Txg|*|#4X)4;+HFfoBH1D2;bd3h}>G?W40sxQxV z_B$?$y8Qa$wtfi)zEJtoi4-Wg4%(7RJe0RHxIuKKKug(2H+D2Rj6+n zU|?WKv6E>aIwa=JeW{)MGi4)waKC@oi7Ml$JzZEw#{nGTM&4hoUv6O2=c}xtV}xHr z(p{iG3mD6jK7fWbxVR`cm>OG<5_rj}I(Pq)qcRC$jl6F&X#noni~rTZNwNsoqx7vD zRH0K^Cf@pOU3V*%6{{F`Or_YPX(6j%Orzgm6%GPmC5?;*9*aqx6lvC(5)f~ zN;HF4lB4F@Rjn$LB9Jg|5x6EpGdecPpmt!oL$i;X1rb`{7|;cTtr6f30Y2^(JX*GO z!~rD>#NTBfEVUvnVTP5(hY&D$RSv0|$=6iX3YLt?{e2p}>y;J4=9OTy!IS=fk`xJpWVV#C z5*p#T6^nfqe(G0hqD*8g@+2(Arlv`By$BPr)-Lbq0Te=qR8m#Mie%;bj zf6dk;IsqK5qNHv43-#=lZ2lfgA4l-Zr*GNvzO%8xq7)ARFd*Q)-wnem2)z3WvbYR` zWbMx0-fhq-Ge5>a1NJOHm;!gn%Qu-X+}hEB5)?kkWvm@Hr5mx&m6mu)Ng7FA%|rDN zCMwNaZ_XO`OqmWc17<1Px`;YCa+%6@ABM2%0xka%LnrKf zERuSG1?krLES>t^>MyV#anQO7*5kR5tP~_gh&FW3Ns7M=Poan>8C%r3&9wxYgeof9sOr+O)e zgJ~0=mP(Vw7>6?ziRnm8p2DxkDRIIt@^~olU#v?MOYAp!Sk~2E(%Og^S*zM^X07C+ z^<>w;h>OM9x2XiGQD%&-0sVf3P$_!e(SAZR*xY(g*h6iFs+^9s4K}+?Sh4@Aa}HB( zdWw2SSrKI+UC>0E9Q-{|1+n5Xfr-h^{hqcJS;j41k4*-@%~IWAZv^ya zgAoyBK<(0^CP%<^On&(R1^ths{Lp*cKJBK5HB|A00?5XSo;UJznfhaA@btj*Dg>vb zC9nd61$;NZg16!R_Jp-@pS_t7vLoXf&!l!TPcB;ssWTllQ}kw!Fz&{UCs@aczyO|dbmAnX?o!Ucbf{Oih|RCStt-sL6F5CpeO#< zTmg=cfLjZOO2I6Hx_5PDSqH#P0yK%iaxgA&r%izNk^sPK3z4#sg~ckP6>@A+vAc+H z_zc#R^x97;dvU@zQrP`n2-=!oR!=>4LjFpfg%T|*66+cT2?l&wfI4X!=)PS@tTSRy zTaeG24VAFRsrPl(oh%4#0Pd>dQ2AmyM?o4hLj-7Zu398KH#ySQ+q6q{(x%u4oHJ@W zQacIOvr1o-_H?);epun?2ROvVf}d-;3?H$Acp53=G=);FV2eDL;`Zw6vR!z4^O}jd zB#b{d(9%<boq0|yiU^@ zB)%}Ug*Ibvy+ zVuEL3t7OTmUZ39%Mm}x(vaHcw;^9!yciUMSC=&QOaOpn4othq(nV$ONCMI%74>@RQ0`?@_oBL0Sa2u3t{;8 zCF2m@p$~FG-Sd8grj?e&hCqKwkZ-lAZR(p^zE-_FSI5v8!?BkyWgd@&_NJg2=yq+T zS+rPjPFnp`g8jqrF$eEp9>WW5*eG-TCh}S^HPdCujS)Urn49M4msH@tPU-^bS0T4I zw-tIP4`CPk_0XqCFXfmzU+g_`RvTmDTgct!m8>{psVqadaA)(k};3Aae2mHP%AeaL}5N9+M3aB7{g_-q+ zSe*+6jTH_D1v(@t_2|Kz;RhZzKuZJ_28X}~VSOrKytl?;VeJYJ@FHKFHrfDQCE8aI zV9d%Tl4EziI_ls##j8(nDJ0DG?czjQS(~+kWQ_uLb9H47PN2?W5@CIqobq3;)AjeI zy`ONxIOxTELDg2Bbk0KX$Z%s%dSVBw=b-DvyI}35_XQ>{H+feC6^kj}F6+ftWv;C6 zl@dtMIv5IeS|pNA3W|OgY~n_=k00p}#R8U3VmRL8z8@+WomntY zLV5Lv5IqVGI$)V$3SFuLGEX{M6vvbQ+Xzj?$R5g z$U7~UEUTN7f+)k&>*yl77(F5v3R{3-6XFszkQl3z94%KS6hg`!ZI1u+FL-5e;qY(NV$f0YNg~h%2)_NB(w`LgsA&dRa%OoHQ0faD!XDm3&hgUaofHj(E6K> zy7RsvQ0jc^!@s}1T5sftGr*r>m1LheDG(7-wB&iq9}tPPBb(bDvse&rtTB;*pV|r!-*4aq9IALF||IaIPz$D zi8!YKuLo~1u%vNz2=;Kv?lS=pKzXQNWnIwZbN`+1Zta>@bP)Vu;8ihVod7Z`DDy6M zhS>iLDFpI6NdnlvfgiAy!iPuZ!)wW&e%J1@lc?ugu~@Zi?=u0YCd@EWpUbrex{V(* zIGdg94mSwh8@q~`S`IaGZ8~O)=owGM%vmy~8;<(OM5_z=&fch&drsqX`rW>tQcJX7(} zl1a2cjQAI8(j&|T#l@d{?|Xp6xYtkh%}o8qIxbKlnWU?=O{ZoHm|kBP`nTz9Rji*B z%U~WCDG&Yg?5`oDa`C3zvJAhhOM*GL+H8mCrJ#3#lrVNaf6cCT z_Yv&wR=j@?B%W!5$JGCJpn`Gp)ALP*1n`+k$r3m9Ujji!zfM5?5Q`++gi(x-iFUwQ zIDcj!3|Zo?DG69n@mbfRMGz+_uq6I)dc#Y>O<_m=+t|Kq1Av`y*|M^C@o<(;lFpU4 zIMnr(Z`FZEfcHy5?t5ruyO8Vt)-2t=-=mI@M-EU9oz?nZ6 zCnP2H{q$=10;(9?$R1FZ62YzrAWxSlzI%_9&-Votd%V)e0}X_xrPVXXcozOLXu=OC z;1>iG(g2p@G-`(GzE`8Qnm+Gn>AtkKYBS=qckIHkl1jS>3h;(@4$M-CFw}EBxD8;} z1s5PGSdK$0397X00T^lknKIrfg5vS}aZfDqg_XjV^ZkMn$Mn&42c3E}6#7}yMf^xD zid?%}eGWBQ1^{`Ez-RI+p%6YtRYw@{6xE+OzVL!h?QO$I8cGtL!xB@;L>dj;A)hC& z<#$a@&E&(5ATSt=zY4yxec3j0Dykn6S4}RRP=$q2NwVodXbSylO|sILzIoxk8F>rA z-}eSlQeg8a^<<=yjWjJWk?kVhW!t-#UK~cA-;F?spALDt1q}#5*4p04(&FvSFy+dd z!7)ETO@$oG2ZD#xD{|ps%ZMI~`(nHc%)B?r^q2K#o1NFbv;GAQobexa1uwdNpXqoO%iHgVgB_pjtmNBYjhxxI#!N*N@+x+~{uroIoGq>V{rX7$ zH@n?Uh%2A-f43|4DnF6JyYWl~mp2a6j2g5xMpq&h$i%eRY!WzS4F(KMSBkf*JB03cDyPUwe2Q0Ces|;X zFr&apbN86Y<4IHYv%#l?`Rz`>^9T$iFK_a24hRSUn#74Gi4RF0?b0ZKwf*|_E4^Af zk?J*Sux?6dp3sF{YBTTijHX+LRCkQkEWer#e!w<#rL_M^%ue-dM)@}HW6K?{yy_1o zsrf}53e&Fe17ss?Rf>(`FuQ{4=Z1*_b-ao^p$CU6Va+&YZuQhL5L!$dH!R&}n~*x? zhPy0HhwI9nk8U}&9{M9Te&p)-`fr-=0NNt-7^mj8)YYUCW~kTRFob6% zvZn!e$&&6{m2g$^HHmOVgAQ|sG?JRK&UFs`5oHx8v0i+|f>$SE!7$5|*(2?@d5O!G zo741*A>AtcJQ#stgBk~a$u?5dd59N}!Y{ozjV)E5N;(TQX8G$lc@uLNwFsbY-oJJD zQGj;lx>4n-p>FxCU>Tm5@{fKle~KhU*0b=93AtGqF#9^fCAfp#Nvk_hHG$MwbhqD{ zq4*{YwypF7JTVP}Ww`Ya7M`M;o>Lm`uDeE(&}L zVc;-XibIYM{PfIF$(Fv~Ri%YS6eg=IhVF#giKFWKHPsK37(t5Q;p4qT>I00#56rfR zfp2xgCdm0^V2M#mfJk>Y zs5H_c-QjofuJ!rDU#@ZHo|${D^W1wMTO=P|1Aj5gOt{xmf?;@&2-crszi$FlT|6GQ z%_x97dnO?~{yNa!fLkGe7s;cxT)8&NzHT+$Go2S1j?$O+-0y>D$)`6X!5mf~ zNo$%+>b&wFDk*Y24$Qa}XM{wEi?r&TSi5JS^EY!X@7g7r*7;%40Ej^&tqI==Bkk8tIF} zb#p~90y{DgI0nNL)cMrUuy##LLW=^OjjKymDY2=!5@6sQq!0_%PqlPP%dCp)3hNsz z9u;{^k2Ik}>#s^`5W*3AHM_Jr+_qV^eOwG4IjQYN=+EAp63BZQ7YK4}ad>C*3oElW z1Y8&7Il~5_Z<}3m;yU~LaW9<7FVfytQiT0Z!Tw@cfwk9?>ONIlUk@Dz|A~<|1cY0nf|@(3Jxna?yqO9N7QBO?rO&#R9%L-$nP@WQ^{xD8Tz zJWcO05C=V_Uria)7&FGr2|)Jmf4DoM1UP2fXzXMdP6Qul^M)%E^JI|mG-54g#q-Ag zPB3?8FU7m5{dQ1KjYzu5~EWpf*yY@t_7~6tbMrgXFKv3G*YTg)m0f zv4T!%S|;2rDm_W@wn|yXsm6<{0eT@hl6ScrM9B~}_(V%iCKRoWLGXpkP5{H;j>Hch zor2sF60!=-mtkR7pQo-b?sRl5RqP6v^#7b6z7d|I1&id|!-FE)7!5@WK?i|1aayK#rPW7>h`=j?3#kS@V;(YgLczaY!CB$U$$`!3Ggodm zK7eN{m9iW0s^TCPZLXLYUZ{u5lFL#G4DxwttU`he1ybz#0&>Wu1R~GM;6G3 z)Z~UVgQZyVD}OveNt`HLGAK6{wB8eMOs_?!&PV+c2gV0*H8}~nS$h3?8hc?1A-7-- zcP@pT!EN(iswn9T5TIzMvXsj7n2#omp2flz?p{FMX6hEl5X5a;SC><*G{w3^VPd+k zfDX4N3x$%z&-dCtNb$o-_xjkwuiz7V0M^B^4U;LnXYRAd*tPG=rvSTT>Tv933v|M5 z1mPw+^o=3O7({2LEGYjkA^~$jx*Dc!SzRS8;#hn{s6XiTg4jbfxI4W{G(}!0pv0>6 z?=_ye5=o2hNi_j6D~8~7EXL5LW#YWYmPf__RBjN7L~`b;qZAM4-_? zPmL`Q7!^*Aw3YFEH>A7i0`8V$yPP@+mr21Kgm$^V)2>B|k3_ebII|{z3oRCEjUPmQa&Tk-&E* zI$1N_10B5h`G*YWq_|^1%R-0{=0O!O;vD3U)JR-vCR^*p7y@Yxa3x*|T(rmVkQ z?vV}m^L?%g@am8dRI3Fu=j}uO%2z2*nt~h_k}x@dQ?FhGUtsnU+N3`OUs0Ts=;UEJ z`iG-#6Uu`0j?Q;P!gK#Vqu z_B3?SJWo&*FQ~3UQmQz6nnBHV2ijVW`&93TfDMq^j8sqzQaQo9$aUH4!z6*}1=R8_ zq+l7K20lJM?(FQ0jg0}^c>8`HayYp+ebX5z)A}_LreySfvn}MAFmMORK*Wcm^GTm2 z!HfYo@coAaBxB{G7_OXSIGOqR0F~Os-444Z(G5IIpLAorhjZxqyQST+I=_u_NYMg= z#W;~A@cDsGm6as!(rpOcEGh?wv5H5p;{6{i^&6PC)4zkbaLHGUV?IqO8#vRfv ztZMbk0hM1uIWNlaUmu4_Q*++P{lg-+93;=+i7zZ@HT?>oOPbzy?l|SmP6VG^`kXGR z|0;H-!}3I55buP~fVlzPe-pZmOmMIh2vhbg0MUO{QDndHi%R_9P4;>dc?!1U*bF^}UBEp3!{`aUTkSyVO<$#p_)X*uMpZI~iSt30uR_HU@wRFgw_U%eE41CA zVMHb8SauxD2$U{!s`w5vnEt8y6hEZwQ9YwFBQ~%P(6jSx+-=u?oBy*T`H5IJt(?32?A@N3g0mpPN6ax1D2yTjd z;45i`PNUa+m}qw}UcIyHNw3S_fo_9yL5`5&XqHI1Q1xh*;LVzrx7`4zy#;i*!{8d1 z|Du&oHbwTjChyq1)xzu_s_;tt+L>!hnQ!&%MGBj*4Mm6^eBmPO-jr&83m$u|Bb$A> zjSuCX;JtB$M-OjH#xT1{59%zwA>=@F^RyNAv;P2dDh5FewHYacR@ORpX9BlMVa*z_ zXMkC3ua($RkM}+oi1MI9#Hn^I`6vfPd&6txBRo3U0%XM&HOvLqTMb`I(mf)2Dx+74 z3-Y6Ix%}LefTle8gNL1*S}D!L zwbx1Kz$?%>0j(ENhx4Jo^Cwt5fd3Jo!lv$Q|E!_RVhe{r9-ma0iDBaw3)RQFqh1P$ z9Ao<7@?rjp`eC5*+!cQy4RufO8n|*yeuV=iGycK-ck05Y%hBmMMMXLe9w!O+_#A}1 z@Dzk~$xOyN{Ahejd&zVq#rAp#cP#JI?oL~p<5>^oyb|!$l=t~Gr)w3F4;!sU!%rJLx z^)<>+&!nUC#%D>a!NL2sBvExipKxF?L~u~zdv&8bfFS?wn@T1780jJPa&Y05nwv}% zHk_>Nn2upgjCAt1p_HN>1e&lFdImBSR?FV`^8XGLWljqATV{AV?Fs*@qne}>gZ7ZY z9Ft&h!h!+z3%WUzO)n{={v;#n6nX+#s#=uQGtnWj3taC;0@(G1HWLxnXCP&%{==>*F!^4sqzuy3dgJ_H*!5H4Zql^OLdeo@0M1uDY!Js-!f|?m*r0$m^@r9QlWNGtd<__;_LZ zbIPzPKT>^nIe)3!4pu9k?!`o@l5DzFERJ@>i!q9}8JQm_(aRuHBk* zSNrU|eG1(0G0bedEmf()#xSO^T01)&E&%~hNe;xY{v(P?sXbA7`KxO43QH(lV$;`6q5v1eEzhYbN_ciW9>+E-CL-5osY%puWWkm z(PR1!zx;+P?U`g)g>WG0(hj|sG6n2?C^)ikA!_ITj6n6AfH+vknoI|-C5jnSG)PB> zR~l3nhasRGpppCFp#0nv^VjGqh zMhJ-{OT`NxGWAcQXiv2qYD=r1a+E4`81DMda9b{{Er_0*o6?+~E6MB1SR1yRaCed6 zWa+FGr!E{Mlu#0q-In|`h*vzoccWdUx8KMV2(?tN-^jv|`!=>4{A{tZU5R0;oA!yK zW8pRycQND(EN$;Xy95?q+fV@=hlQ@nwEXZ73Tk-7QVQttrFR}hJYu9~YZ-N^i6yxa zS>#FSrG>R)t9`gFW11?<%mm6f(sqYmEhEP|fgcKRAf^3)3jG@RSliBRap$os%gRXe zv0TodTaF>U!QPRaA4;gHkTLsXs)g<2nxyiFd&0>R=))JYG=f&fv#(*_BQ;+kC*yqj z14a}2g<;0B)ujGwaSowOPfpRqwY#yJ>op@T%N_w92*jLZu|r3JJG@g zmf0Z-4n-1vkCKWKFMiZ=97^V;D)pF(7+<94CX=11o@R9M)6vk&r~>fguRV5ku34fU zm?QW2%Rb#5%t8HtSWYX_HbfdC7zxV5udnFpdB)juE-gp0dhaMd6@^x>Rw|acPHtQw z_S+w8^2C+TpCP2M^phij^)uuv_^WY^w^gyv4 z_2bm=bFlAe7tUGlZp2TBi=OEMT;x+Z&#^`JftYs{5+9OMoe&>y6k4|DrQ{O2&ktP4DK=rS3lQH*Yckh7{ z@3336q}IsfQKgZ+%9XKTHa?7yDw7}FkZWU0o`Wk02l=1reMC}p?ZI4NQ3gMN3ZG zF@le+(?~OWzQv&iXJupn%rTI~E;72$;C5g$=mezdx0A2_3bzQz%&}$wz@x|dg#_vb zNzUQr<>k>4qc9f-tmaqRB?FRQLql+Gr%IxG=TY(H672FmmjIEn=6hTVa-fU>1xQ4+ zvL~oo+-lH`Yfk%a?SSw|?EC{05qf%olAw9dk@x9S(A_5x4j0EzQdI>)6}Wtx6Q53g zs@Lj_?>6rH$hpc7<<{~m^sug+dbJz9K}Uj?^gr+rp+cDUhRvwi{d;`@$oi+Bz5D3z zbRP1Q>ZKdp_oslt6-+_fEzu73D-c3BRiZGxd_3ef%mu>)&7zHdw|EY6s{cHB1EQzHzyS@DDDyZO7- zj4oCVT}xeDm}@N`%*$=LSY8U$98J`I_FCV>A}A=4#t+kZ;Edwm1>CPnP$;3`r32h6 zu%Xq}*PpF*+?oEnF;({pnn@Da2YgqMEUN2zJn|Pg%Qvx3>^duab&3a^*SCBfP)4hL zC<*7A{zI+3#3ZzHM6P{s&wpA4E=gc7ARnFL%u)gtt2705oZ7m&E?`#&X!~wlXB0Gi zq+yy2BP>NR&d$Z$r_Uh4`2KI(9{^S4IseD}IO$hd8|&L?LXT6^0#YE#&c5@&FBy!N zHtidfTrBD_BGCHn+r^9|dcZbz{!IJz^ueMYbUbGT%T)VY91E2B`s1O{F!Y{Zfuc6= z*N6S%2`<&|&|vcej=EtL8kOYK+5U(T=z*HGI&UD$5{}I=TqIZz(#wE6^3sK3p;9PX67G{Z7DQ zffWaINK-)EJRsI)#&c*PO5<;oplZ{iVoW~FfbP#NABrXIH#Mg@Pae)>_(fP{Fymzd zDMjELpD!~fiRvnXZ+q;w&SOwx-MiSZO+Hjwyhp4s>|$K(I71C=X+A3-TOZ~sOz1cd zhuQTs1yK6m@HqRR31WWxbN=DMydOlI)D7cYvnFQqhg(>22;v!nuyn~FsYr`>@mv*4 z6qI)pgezL~I0s1IG>XPlJzNWMJf{kyK}Ymq{3hw5JuVTF*4{ibwd*65{NJA(1|1c& z5RH;nc*CLRg?+*e{oN#RK*Ev?6u+2 zV~d!(&iZ-Zk&?DB4oNUB3t`%@Er=~Wg=z(3nZw?NZSc#p4tPo|6{q~9cd{!~%goAR z$QX~Vde6|S9EQ0Ox0B9}0^ZQ5L_tZ)ynWob!FcYl0K9!Nxd({}$e~zlZ|po*=+)3^ z+kt$R`vsUEC z=ojgSz=c`}RrqxNQpSs;Wg_k39DyAKTaOYW8i^FJ^+BkMsB#>A$A3>|Z)nWGF1d!G z1ZlGg-qO7=B6cGjUJ9DD`xyrAWFvey)Q&aQ$NV0-Ej9`jsLDvhh|gRUGqHOtY0S$G zmwr`k=^Su5XsN|1lP#a^{4|a1NI#W$@}x&9vnR9kHwy{D<))s#V1&Va1$U?Zpn%FJ z`L=CfC=53(R09d1q$(TU=&53QBG8eV0IC1Wkyjn}o)9(mLrt!HAdV*o-*FSyv~p;c z1(&P%CJ${yn+d2MmRltL4qKvfNoKyM^b_6J!2-RVc|vJer$-iXgLM*th2r3}@FNI% zy|!t$<<0#bv6id+$p+)~Ev!FmP$jGRnPxHNGstF!=)EYV9%%G*!)Y8Y$1Wq{MPc_a zrs;Q~y3!v-nqmKrQ&NN!rP7O0T(NJFNP8D+85sgMTqJ<{4`H$T^AWH0!eM09eC9gG zIT2%_N{E{+my>+AS4H^a0{1ow&xu%Z2u>!~5b&%vq+)-6KQzZE0jt=&J#{R1V+Y52 zZ=7e=-R@O4OAw}k^Ao1RF>fq{Ns*rodhpXdsP)j=6I~fcXSf0Zc?Qj zVV8Fmk3JO7nvvp*MQ^_DRoZ1{U^`bPDIsp$jbe=MJ+BKg$Z+^-kcv)~J|GFn{hl%8 zmh+(&kA$ixv>FNf{ov||z>L1m$@1VblWN#}n*-%&p|(j;VU42_TD7jv^V(%#2b8 zsm#J+RmZFzN_)EMOhk|H*(BTZkuU&mXSNF`;6iKd&(D4(k=^IfFk5lMN+FbML_Neo z5f})Ze;^?p#nweM_-fQ`juW@zdK^|}0{u06({N-#?6Gb?m7%Ej=ARou`2oVZK={>E#lFrHBSjf$CIxoBxjjb-AO(=beLStR}SDR za4vlGNbafg#1t7~Q0KlSH4+j1{2r~fB?Zk%X09i}<*S^Fgn?t?tq^2LSRaK4CJ$`o zJQ)-@`a77>hbk^~f95b+=f_JaR>SJavewj<#8dX8K7!qtsH8YvPmk6sO^7i~wXY_9 zecD@o*W)+MH>Zo`5*h3}@pM#)OO@0_E+o%byb+l8^SYE8Sv%iChzS(Jl&SE<6%=lt zlI1^Ai^fb4IiG9si5IIWciDGhy|4L+?3qCLjBB7>s}mNtM$rFX)-(UFion{}?_GT1 zbakZ#?yNKcq28#8BvARO^=2_bwmQ89Viv<#4b%rQKMkeAXZoiRSASPNUh1K77F+&B zQ)UE~hyz;`D`4IR)(!gEs>75|oLWBQ-)Lqs>{GQ0JSTkDRCY&gC7|d+S^A)ae)nQ& z^2ak`nTIR?fjrGvm>%XYPDx#rYfvssmD*u!Mk-qD7xzj&w6@CO@;fqsjVkGJvB2D3 zZ5i1|Zoh(o4aH?AiKX-T#goSQ3vNF&@pE32$#2a^UB^EJzqyW%ew#$N&zKza_JH)l zh)olSmD|`vN=?rZIsUW2S?{BFHK<+$jvW}ZSgA{iPD@stqbl+=tEChS18qJ zF#r~e_n-b6>FYy6p@lY3NfXvIOYH0=(ivPNYG{MaD>TIVT|fhLxKrVZO-VohDvwOs zj(v-PtrkQp+wWJ7>vD!1Y?rCRQgmW$yDOc41%wslP(~QvfyUpXk(aS_Z}H46kNAvN zOW+E{A3zn&`dvy$zNY9Owc0MekCHzzE4ddzxuiObA>>lz*%O#4U{ca~+Z%*t>SVb( ze;PbqpT-Ff`0?jZpb;4f1-^n#t;>VrFBWVNWpUKJzDElT0s9Z84yORccMH~?*dm@% zFA!FD*l8jO$t0w{hz;Gc(1SiMl~16r{jFE)=w?;zKZn zEfH%%U%X|W>H|(4WFA|^HEs5OfdlOLPbfLAvSr(Bxw#^2f9C(b|8jgYuP+;cZRzJX zCznb?H0RmRZ+||&{|hP}T$q8&kq>K{FDHg0h)bHO+hK;=+nusk#qzgSeZ^m&s`Sz> zGdcc{4vfFXD{n?}HzI_XoD;CEw^nCI` z^u7N*yYD+lf$%bf9I^Nx6xDAY&Nl=dgxL&=+gRD>0I4`66Ac6cs_fryjblKn5cgge zA8-!e)!(%bdwv>Z3k-4lm?VYctqhXiU@Z?u)NklwnjCmid(uIa_nY8g4N_3AS=0WJ z_X4bV65BlWKBx_ZSxRtORKbKfL9Y+;kj2CQe7ce+KEv(T13u;Rdd9jRsa=52ZkjGAY`pIt z>5v>9jZ3PxQPrv8%{J>g1N6YakiP%qRT*8hKB;O=%x~k? zE9iIk5IDj=;ZtW+xSgEJa7)CCXWKy0*;_!_cP7O+5%t{RY61 zjCYcd9pR$t=+9)nh`bvDcfY0sd6)3Y&UTO9KPsRx%AhoyKXm%tnFPJuyitG;4s7SB zKPeP)L7@&rK8x(Rb%Of1-`kX;Mghu-fG0sJ7NDKnDk0?ee zyn!KBER$SH4}}s2QbGzwVpACcvG23m02XW``F1paZvj8Suwu>t*R5lxuIG);-TGC~ z#pu-gCS?VF=DR;-&XkywTP40A z=#bWXHOrJ!aG13FKOG+0IWC&i<1>Fle{p3eFlu*;xEc*F-Ac~WYeU;kiF_TC1lceE z46(`8)&4|IRQ5QL#f5~!88zGN#pe0)Ri=Cxb1tNDmoW8H=#?lRUsM7hLpHaz@`;M( z8!BR;be3t;s{RqISX^qq-5480D(+?b(QY7^7_3`i885r!*W@8wg~WtU`#HEC1KGgd z((e9rVjKGVDCm-izcUl5oIrJCC0Eea76zrv;Cp^9qBd#ei{Ma%uiKM)mge2j-L2a8 zE-U;BXFd~&bz zskxX9UORjdiM)5%IkJ;)VQ}w1#$c?hs+vE9n3W7)*oMiQyWjf#`}gG~XDWszH9wvN zxmrku0D(dbN{&@8-$~(YD~A5(tubFhj#TDxA$^i=Bp$e$JIwS5ui{>kfBwPqH1PAp zFKlyVBz44pI~fIq+@xdjLZxk(9L^QK6-VAE(0(&onQ16PHmdim_!ZzkGg@!tLC^1s z6p2<#(MHsoGX(ZOha*;*3C1Q~g|zu8zsTJtdSOd)t?_cqe*-R3I1XbN_pVP@XQy)h z;DJVq+xS0uIBsH;*4=75$`)t))x>TE=ww6*CnFa+-lheHkj3J(FIK-9XawsL5$MN> zJ|>mY!m%vy1d$q92vu2~Y(EOd1g zFC*gq4FpTUSTN&3IS&r;Vn*qfVspu>&ghmHlVoP;mP$!!OF_{PeT{yo0P2_DG1aKX zY{O83QBm58zdmTENEf-bdq?UK`NvKsm&)Cs-p8!N4n`aTLv5%Ln^k1!)3d@!shex& zg#YTzW~FzQ61uYkNj=xH7iE@fl!H7jQ;qTH0u{w~TFGO^Z-L7SzEZ&Ly_QwP8;-T$VgzEP2OM<$A`JM&0Kcyr$hM_EfXa z2owLKe4xIQ8m}hVq&4_Ss_e!6U-1hgDfQ$$M|hupDJ_(n-i1@F?986}D~#%fIYA6= zR6titidr#bGcT+*CsXSm0Y%OT9L~g2BLW*gc7p4tfbTkmZ>}EzTiE~e(W}tP3@=vE z_p@o%*G9KBn@j=-#HqYHGpl~r4@uVShjjxKc8AL21Oiss`3oua=t8uF*hCMwzV7xzDWKVlim(!nz$TRXlSe&oJQ92jB zd+_0mYMIGmsp0-E(|90I1#yB~j+DA-E~o@S5*^(;ElM`;CnmCN7HX{i`&)6D*)g$6 zuwjzfzIuIcPflu2N>QFh(OrQKZv(5&8lP8VB zAgw11+dy`9d^$U~v^9mc!n($|9+HAF@1RmB6FqM?6uQr7lWo%t_PMSKsT7@a22Puo zpKjatnfKl3UyPT6Om5~I8vVwCzZf|tIq1b~&E#Rd!B6ZQ_3sY9_9=KDthHFvSAi&= zx0nlWqLsYM2(b;lmUBW$N_ET0ouWuZq-SIzU<6gf5ho0RIqvV@x`Nl~K&k!mx$&`C zhP*Vw1yupLIm2DJSti{`RC|ISrWQlpEzQ6MQhb`&o6)Wn7dh@kK(oxYfP~DGs8T%J ze7Q~eVtFm~?cq0ljUtW_TV{7LC!gqV{rQ%pT@l6>Q+){yyBOz`1UX)da&Gik_5T>Lq!NMF9@QPSoK1dGTfon9lDFuZ9-ZEl)k?> z7I*|@beU28cLZuT#B0_gsld*JDQ~`7}i$Hbu5fH=vO`8u%!WGQH+6Qx13G zN~9S+`QLA6jYTZ3=j=v$Z2GROtc?9!{EG1rK)I^hj1n~%nH9e`RQU@*DPzdMxwSN~ zO7!Mfugansi^?*R?ec9j*I^MqpnVJ&pQJaUF8bj%ES63 zxY`?W%(u*@aAkhgGld+fipYHNrjIp8Eb}-$J`OWPC~fp7l)p7Rbn!AX@*yg&E_t7o z3$W-K2b#>a%1lHJG+>IpZ#AklGJ|kGiP`1PAJFQdIvciCr4gu#)s*UO-@UTTS+0aY zuGxxDPC&4cv)?3>20v$kkQp@)qZYN&+xjmNhB{3vJyP$V{(rvI)p1*`WY}cPd|uzH zxXg$o>N44iQg${m0-auKE>JJ5N5~jLVI(T+2`OLu!6%i+4OG=F+6Roc`XfsZrFbUi zmZaT_9)?R*8^{HFdV1b~`r!O?KKu5l-#56X7r0}K^)<|>4Uuv9Y=M_4`8eGp#@2s; z3Ib}v(o}_Sa1?vqUmeI4U%VB8_4Wm|FVbc<1Kk=x)c(EP#eLZF&HVcH_{JAtzTT*5 zApcK}${~(TAe9N$l1ps2~+~cWl_kXw?8j^`mf|C!IRm~Ua1k}dclV4Uk zO&RCl(f>7ecLzOQABMC*R%T5XTeS09!R`5UwYJ^?fg`D|K(8AoWzO2-K8YEMDKy>x zRzZz-RfHw0K6oq!hjJVRVY_^o)NCGRqe6*;he!G^2Ao^{GSx6N3d3MUDW9}ECEW)V zJn7j}FM!I@{$c6lq>@ewa;MN=b+c=7bn^aieehQ3-uO2YC35{^Us%-y$4O3W|G4}0 zxOg#hQ@0(tJhpSJ17Ct)Na)Aw<1w)QPtCbitD_`gzj5V(pvP&;lGD1kU7x!xQ~a%| z=?a9TEk2wxLpD`@Wl|PxMIgwFT5;<5XlGaH{kN3jPeWC-R7)0@G64FX!D{^d-|G{Q+3J_WaLga|({lwq zkAM)!qjT_O`W`>B0q7xh@6UdubcT@pu?n*Ih#1<0e{KHZ!~SvC-{UvN;6TTOp;Gx< z^Bb7Zsw0p!QLX`m<4xfU+WQa)=UaO*@FqY3DiE97G7DF@n8*F)?l|9uKUYmuoU8-< zp(lR=NMm$#bcnvpi`F)HdzjsTW&+U4^%6a(Tlvco+-zRocnS5-WA&-l@;DIz z*8Si?KLf%uZ+^^JNqB_`8RscfY`ac5Q9(+g-}tRsAUAzW_I092M{2k6QzoGS5@s3l{C) zzI6|Srq6@>pdP~RCvlx24|StQ#1c~$&6i_p!YQvggM}$Zs%dv_ZGP$QjSqfmt7^FF z)P}Wqu_g_+cIGW|0RHrXl}N#+O^8NGnL_KfH<9Jk%EXtjEN_i^VH$yND_7JZudQ`1 zWnY#KMe}Z$B>LJ+o4QDxFVQ%I&D2CsFF0k}+2X6YhFde$;srZ6gg(3`H5b3;9?WF( zu%{q|!c3ch_sB%=A?ILBqu|x{H`06V+DFAbdt1e6g?=)V9TFp(-;pN)SwwS(&--Rb zmi5b$hVpw{>TUTw_y#iSIPUK5`sn}!a?fy!nL&{fy$4!g;rr)`ze{XEGp!nbcT31N z-#xmpMl+?+7$&A_!C6*DfE0~M)mfw9JNI&W&@vG8!6oi5eK+HDdv^yEB67(4g8$?L zso7vXu@IkN@Z*Qn`+p}xg74(l4lHwa3tR!wy^-URD9rVJh9;ScmUna1bcC01_5+66 zL?$$RVup5y@@czAn^rg<5|@$T6%WaMnH9S?$}jB9)3l2Pn7YaHUZi$gYHKW-R<9_p zf5$DF@T2+_i~Nh1fgZ--Ojq8%9)k?e)*%{UMU|C~F4{r6rwm3O5SK#NV5xV`%jscP=gyz-HzLJogMxV5!)I!6FoqTmL%aw#=1 zteTVKy4aRu7|hnmF7Qjf+-TKDvsnu+?$*%(&N7uX3R zhz!-mL*AQG!y98 zazMdldU2>}s7M17k zNAMm1H>){y1T}6hka zxiQbawC=P}R|~=~6tna!?G=rI(DzC+c=cfP)g;%S5haj|unvJ_@Is+Vl-vBT(*%8* zHjS1#VAS-;t7Ak_ufIoOg|C(#u0|7fX-;l2y{}<_V9)VWgHf|tJe#{1cDtH~2yvZ} z7yGN8fq>ksy`7!(JxsPvt>6&75t##2J6wMXlCxeh-W2J%JcC*q6SRonT(mI(ZGj;* zri1BGla&m6t0y~?^0^bNLrLO)*Be8N_9HfhEnPTxKjuK%LuaIu5 zkl4?#wpB(|^?JeD-8RsEc&)qw~q0W(>;uG|& zjsq|Q7Wmj}7P;e%LVz3%P9@zN8!Ia*(<-Uy32piA{v;S;O%x zQrw(OjB5qPQE96Yue0(wW>E(a*ce(y!G!HIm|Q+2;@e0ucBISl{O09HeHO4Ma1uQr7DM`?l}<99G#mg%ooFjdLYXSd0AX zr{lB4Xhxr#n5~0QqVu(Md_rif)3T{PV)gOUKKV#YaMnVTu!qk$wbmQ8H#Rl(3xtqA zy>w-_fTw^>mqv^sZ|=h&NDU7U4}Puq-|e=_GkpB`F#M~7ZDW9iH*8t;oFORO)R)fMT2J3q~8O^Ft> z%=_DiXGBH>khJ86HGPhF%_zi-#O9TiJvH*lqGE}J--jE=YE7t%aXDQrGY>hr2~C|_ ztk(4`0`1UNkda=_&h;y&GUIFus%W*wzScLSjjk6hwqtGtXlLgyh1v;27fF#XpR&di zBK&TBg^~6{Fcu1-dV_Y5V&c9G5D0U##bd7-6h-oQhl+tLsJ<{$&MgAMZ5j=8~__NrWhH;0#9njh6^jVvdsyBg#!tdrJPO zT%n<8`55!SvJV`{OFBFRa~qk#Pa1A@$yd#2z9#;dYo2cW+1T(fm){_!Y+{n$EXld` zueT<>SSPNs1?yj$at~jv4pVFB*6iZvi7S8ew?7n_v$gHL$wa)2<7ugdnnVYuGh;8b z`zIDelcO{R_D5PCurz~xeK0Xh-*0wsa@6w)HH3*-@6U9}QRqsT7zrkMX*6~Y+4VgJ z=FLC)7xvfeO*)>-R{U2gq@Uur&pow|Bsg7<)5=mCo^{KkAFdczTAEr@!NS@q?7AJU z0+r3x5iOv^8q%d2DIMjtTj=ne+3}xD8tf`lwZZq!PE}a^>3&J$}K$O8;9(1{;dPRASDBi6wbN@q-*{a~ ztwSvZfdLSp6%th^ZBn{o8rHxVDgq#M5v5wOKSJXa$2Jw5qN{`?7w*z}_1m~`Q5(0k z61hqIk!5yKLY#xS%{RpFW(mJ=$i#Qqq8(3ISfoi)GfI+=Kt*UQiCn~ReVA%^YGWo&zjt{nW`e(1 z8TR|=$ z1d6dhP4$1JFnCH*k@I8QulV_H1lye=I?XHjI~3@$Ap_ z^)A5kGb1GVl5KX;0n1Pv^W&!0`31}t(xGbxtDT*l;HfTm{`Gu|LgnNLmSH~uklizU z;3hmTFK;lyrVos=8+fnZ0!immCpFwB!URk+&ly>`GO#}E1SsI&1#SX=BX*vM0DW0` z`P0kmoBOi+P}x~y#n1Un7fqtR76*3^Zhs2(9ESyt=4*9xg@cp)QgMwYmdYn&Rx71G zBTjtY@94pq#l=)<$o&xE6?!r{+PN7ox7Ios5qG30aDIM%TBXxhBrmo>LGNx{*a?1s zhyU(zi1nKA_SCyyR=NXDTb6liDIK?BpLAJ;a4n@i9Ns;>A&6C97F1sV%I}Lu7N|t2 zs`|Mb^gJBo|1fU)_q5hFxZ|zt4ak4NLQQG7#h`lrF(&^$=2Pdfv7Yo8vQ6#l-?P`h z@ImMB;4}x4MKl*9-#Nc&Vvvn~-joM8?z<%bCj|hhDY-0AgYle_w5gsQ2i}DIv z^a9&WZ6<(X`YJ3e3{>Xb-36ZAg}T4lbX^`;iY28d zcC0F@6x!<|cnp}v-CSMO+kX6323RLnlOOb-R9#4!*^Js;z+ML<)R)~wL~*(MM{_c2 z3N6Dit_sX!r}^QFgvCDG+)}7+|E$sl?XP%^&`?a&4>bZB(b0%jk=SMyf&%85ugMSN z$PO$$*qWeJB?9-$cK1u*yP)mf-rn2W+o>ruEd0D^=Wopj-UPD8HCtO-6zqXmj&Na+ zi&nGPv*j4ZnLL2{2`-tYr{c9juK6r<@$xZb#Sb9sf}P4hWOClXKtT`x6y#ugW_I?` zjc-eW%u^oNYk$|j=;Vaf8^bXRynb}c zOG}N+#WrdA6BR1^0T58Cznk>-neLgp^5zh-{9#v_?t&#!;zWtzt9M6~tVW6knJ3`p5qsj7EJ2@8Hd_4S1o zSk3wjY`fSb%Mjgi!MI<2z^k(9RfPW-hxVf^ZNW-GS4*KPhnJOq#)vbK+&7js(a_pX zK4PcFQF&#BaFl`tTx7VqkWLYo;7W#Z1+Na{)u810RceFQu-SlA&hd}EjKOJ`2ozZz zvDP0Ls9k6Er1#hF$7%>DQ6jEq#?p!s?HP2PKRQvqu7>nWge%O(t_RW&#miTM9HLrf3)!~*S~A2iOo~v$09!An4$I`ljUA!>g(y; zO`rsqwJEUwEa%Ixh;I(uZ|{}ACpwJ(+{dZ+gN{HIiZ|Lt(OTbvzSlhsMa(C_Q7>yK zM|jXjLm_I(FV9cLy_X*90ADKqW+O6f6ovIm>DD7_NouBso#*jD0TYu-#=by>%>y4N z6C+KR=AX1)abyqnW_`P#4tUH223h|wQBck7T&SUs7vh)xV zWRn0-^g?e^Xxh5mj{<|FR_tQCytkbq8PfWNv`z1K!nDg}>rD`mamc0%mLQz>e5U{* z^XgKAOc>{G=pWkOa!Gayhnt%a{3`L7DX5-o?NvepWsn5cR8+0Z&Oxz4OxIV}>-jrk zDivJjKgm$~3rMCX;g|FT_`^}LZ_^DD43Z0BIkMCt{5r|3|C)!|#u-g;vkzfPgqs2{ zxKgU5W{vFE1G`4I7OhX8KDjp0N5y;MSqM$L+ek3#DpW=)B?1;~0d-lbU@jV3h*e$Q zPJ^dqsev0~lb(-x#JHIkYPBTu(w{6v&cA1i{;u4pUu4KDiU_1#P^~IfxaG$$kkstz zROJ!yQh0EV2CLQb*q<#v3kUeVcTF=Bxt{nA=gLY=C7>|&$G^_9=#)7=8Kra8g)Gn^ zMZ05w{rW5Bgdv;1V?(CRsea(73`-pQs(+_PMXHrs2fJ91hc&*|KPtUEQy!xX3jG<{ zPvn`i?KJzMv3QmVc=za$0g|>~ZY_5@*;AN~uS>$dH#CT;`i{}4p;Hoq_8x5mIX)Ck zRW~@fLx}kGIef`_k{`7EcxA|`lv&J}eWLX4tP~uY%~Puq-$;xrvs$o^X~I9x^kk>v z)s<>-NA>siLY7Lxh4sF^zJgO48SBS^p6uI0|3_KBp(JZUbc?38tJzu1S1IehEn74S zu{oGZ1|;boaUCxY#w#;+TZ2l9k46N#Vfwz-L|y?ltP_mVb5|l3yX(w~IvE8GMTQvi z)`^nB3vn{?B1QhakSEP)u6mR8u0g8O6N%O_{fL_;DFoJ`#V|(1zxXqfM%uiPLSqDT zpLUGrG+kv_Ra>_f3F+?c?rxOs zbkiM5cej*uw{$m1NJyu&v~uVU>FzuEp6B}a;Bm9o+G~#SW===fV6Au~dvQyJSY)QU z0C88^I8ub-Ps8LDjY6MT1DF@r+NGXtg$ozDKGL}YreTznPgdGz%AH^FxCv3LCGyg3 z?d+gVOHkiZ;vv?6Cbu5M3i90n5=Iwiy3Zlb6(4kH52dTKBjG+G_sz2LvK$rG(^Eu~ zPUHxjr0g(|K$+6;iZ@o!Bi751o1!oJ0?YI|GiZ3^&_a#lH?4l)s$!#=G>+;3a)7?N z6u>)QQvEiw+`E+!j+rxLY2_??9QDRcwbgAKn^m)$!qVHHE^Ei{W6{8BMkXN3C{7(^ zMd$j-4YnL2oqqj>#?wE^fu{gISH?eIP1@WpjG z(aB=-u7jL(u6->g9LxjJPM$T`3=}xk3tt*bqeF zRKLJGxJEx2B6GN`C?4hg30L&LGj|*U+g%$PX`ee?42Q7VySyJ&Nv0UO%u>jm|3*8< zE;LwjC)g#S@hJ&c>g!x-Fj!sX1V@1647;lB2w+zHGPO|q&w^P%9W)yjY{Vo-?%`;` zMsX#W2^aUPTGA)_3`9SygCD%@oNj`%&#!;p5uaaQ z|7a9r)@-#eEh#C(OdZoo(chwOxK4NOkn@+(8t8j>)}p>nLclb}l9>QlHn;uhV$guK z23RMFGpL_h+A+_EwOyTcm-)uy5P5~Z+AssfjDc*Mp&=wjH8Z6@FmykfPVSE~xIP=6 zC#C`UHMsTZ33v!ixx4dnpFl9uN$F>=$3qO-g0=#BdPk3^tA$EI?0Hg}P}68Iy!kwy z4Y3)v|I&5)xeQ!d8*D&*`PzZ)n{2E3ain?B@#~21c{* z-<^Q|3p7W98sbz75;59?h#U=1ynO107gb$)7k2zCfC8euoT34`1Dr8SMI{T4EAAd1 zASN#muOA;UhDPA?dS1)Z$8`U1jDB4Md*mxYnqP%yk^NygidRcV$FhC%_W5Wvl%_MS zIlda^m5~msX*S4Pcojo}k_N`TZ#zWlq#&Rdy|)bq*%YjOK%99-_PkCeUiEPjGO_?n zgK^J$aj6z^m|Gx4`FFZ9AV~rLY14IW1O7k2*~n<5E0E{oO8TbV05l$T7@mMDUpNXO zMo7K`Lns)u>+0(ZJdq3!%;8(M^vPtg4OP8{H0F(#op!U8Fa=ibRIxz#V6m77s3Aq zbNtWMr{m`KZ5BX%j?^Q^Z-Q@3*Hfy)9jHZqUDfJ8jF?O1ND(cPyIugTtw;MN$!lIZ zKS}Z7M;wD*r)|nC)qSutUkz~dJ0h!G4lxLP{=Juy5o}Yp*SzZQ!)=uB@jd38Ez1%D zS@ztF>9qcaEhu>pNIAd)W{yE8jsct)s@|LayDUx1``27Njsoicfe6Kp+aoD}*82~b z?LdYwhjcEb=C^iz#$R)NeSKjm0~0dd2ryqqP4 zJSY^O5Q4f3d?FR!yLll00_wRU3vOpiMMKH3SW)ruKm8bWqo{} zsWFds2p&hDVFkk=cg$*edw3I&WAjQ@l7U_u0PK{Q5Idz>uz62K!9ybAIkkqXT}4iU zXClSnK^CH;g+L1@KWAD2Ra6Ya-GG(wO=obAnUghht@HC0`^MsevyIVNyQ(BzNT8iw zlz=Bo_TNgvy}f{G7*&i<{f==DVO&j~84% zv^cT)BwYt>vmYoBcm`c^pM8AvM(KZSi^)QkmX`VQnU0U`O)UF+8AX#V5P0=w-rNpD<<-jC`+Xd#FAAytC{8}kY45xs(CI2g z|D(B?AxYQ&>*wpnXUuF$DSP^q{&6!pVm% zA1DmX?0g*N5ey^bM9olES2B5nXS354NYnDZos?MUKZ-eIgB-$a{RkqF2FZ>k>k+RN z%8J<3%z_t2)4s>hVw{vP6B`Kc2X_2HydHGuC+$J|h?|7ZnjiA&kYo7&@z++>!mn>y zPWb%wHqAEjth2$aH5C{}?Ce`)O#7V5Y=}T2%k#@1RBf5MA@bmKuQzfdSu>6f`2l6K z&Tbnoyg7^(^aO5fm^$)Sv5vCTN;H6HNc$^^J`2?G;l4odq&&*iwME6~adb374P%z*^VBEuzldWps&9 zTh1ddSCr zEp{KO_WUN1%Mz2^w3qhwQ z6KG1mi31bh{uMD_``yPbk6_81|JMSfa@mSlRDJcU8L=5@)?_PJi4W|H4E<|Ez2w@WY9_i*=a z5vYyJ`qR@*)$r%=sPaT59Sw4+y8;of@zP`61}C)MvjBexB_CteX!y!JI%nEFdS z3{Cb^-JL+F)BBP4K|l|pRcpBX=MO-ymR48A+zw{JJH?@OJ5{r?_?fHhaqC5%Czb;o zmNhjs;&1@+28Y4)4b)Kl_S>7*olF~X_&lT}oSWiJRwuZXg`v;E`N>Lu?;nwHrM`D| zP7r9$jPCWis;dkl7obs`(cZZxmbSAkntk=kYQNrf?f+l zaqO2ep?2%^;(DkKq((*B8D}AP#xlf2vp6($HgY1tNRQ1Rsmhq(m$|;K==APqz&#(l z>8rl9u-9&1i%bN3l~PT=6eviUIm`%pmN&x{fJw^0PyB9@j^>SvPOPDNcdQKJaa|=< zoR#TpDB|@N-SBr*&z(z>|7bCI168n-6GoBYjJ1$F-_pv8p`oEB%ItKVTPu(MX!V>9 z;ci5&7xkkFQ)JNes!l2iDXHjbH}q?aD=#W+LC4IE|5ZQ7K;D^;DaYfh`lQ+Kzvi=X zaDIXVYOM)hd@KV}jQbuZg`MjbyOw!IPg7w(G5PeUbBl)U@5;(VSpJoN+S5HfdrYIo z6^uakQ-u6PHenU2Y;=NpbdP7vh)CH~p67>{M(NK3*IqvjeG;J@Zn zL1WvqVe5$h@yrjC=R}0j7p8mFYp$(8XoLMOExz5EL}LhHqEKZ_XI33mXU%$tI8z3- zE`|vHW~#Hy=u?AB5=kl=<%;$0{eipp>q;sKo>9LfmNgb`TM7@f9;yz4)iibIkD3m` zAHr{yRjlz{$R`Z-1)R|o`qFD#1@!#JDRUFHO}b+g9_tPfPwX2hmE)?h_`~Y}E&=V~ zO+D5vHK-$qH{DZfA1gA~BIdQfCjYmVsSbhgh>4+LFtf61YTDzv(F(qNKi0r9c+{Fd z-rG3b*N^jHaM@<2ISmqGzjsiYMFtM5g5V_2!o)Y02 zu5Ls7h2PIE^D$3NG8P$wSN8kb+HCW*b-s_5glm%nj`?qnRINjB?0d2|Fl*Fu`&yex zRf&4~jA}&PB)%(c8o&olTnATjG4W@=^maAX*E|i$&Get|jXzc4$IQP=i7$+yet(ig zmmr7uDVfNTB_vQKDSJe z?>Z)^NGK6g7A)}mUB>uY5L!~4a9%sgwQ)>d&1zvC4+2p`^K)eLtmKh8_XHql17nV3NNEY-Ha%ic`=^!!`7 zPJ)zd1W_P}%YmnB0bC)*qzV$zVENN(aIH`{t*H&J*m))81bA z;-aW~1aw+gy=r8Pw>uT3+HBA*nkhb0T)@;GLktyL>+jeUNOP)6tLq($@M!ZTg0f8? z9g#j0W(bsbL7l%O-sX4z#wB?!PahqmbbS@;2tD#WW-Aan&x>Wyt9^Xh+k<-5-R8aS z6FQE7_d!kQ{wDHAt4rTOsqA6$&HKw?bp?{C)cxRvfTT*8D94oP4eqB5^%Z<1tkHQ8D;7A>|eExgHC$t z@IVU8q)P=m*pw0@oZ?IDLeRt8PHAkvS1>Z7v9hr>ROjsPdV#G8oALH5 zNABP%xg0ME+*zZKg?s~hqUYVYMJy37RFW7!zqXN4S{wP#uWHx>;A~REnNy4U^(Pbw zFE8L4BR`xBggWEDB}2#dJ@ENw(xXGBI9 znG3Uj1fpN2WXSwR^;Uw+6&S!83r1>l0>Tr|At69|O|_L)H^`@2fUWBFpAtX|b^8#% zbUI?7hCO=>>_IEb%TG(YqBp=#xc&EE4;SiwE1>zEbCC?fQEwlBan5;A$|j*FqYD_Z zUXS0BtHGH};z@LwIk_JF9MB5;0ywN$KnMI#_dlExvN5mOXk1|$s_cc&V6*dmIy3^#IB|HdoJwR#`1R>xg;$Ls==bUPQ7KQb`tr0I$3urE z$0>0IE`=W;H@URj68>sJt_)VM3_!>?T}ECC6ECU$m}I1*m4OK#FYn4(tVf-ZV~G1t zC4n0t9c^3auy=Ce80Ea20r@Ktu2 zT|JSd>JO+#>)=!Wk5`+Un=@}!oP2(?!A|Ak<>BE0PodxHjUSMp8pXyiBT72GA_oEc zY5sDtc&>tfK>XuP0F!+Q)o_*Qc|u%#b^NYCKXb*OzklyQQx_KTC!}?3@jX{Tu}cXt zSMWr=LB@{?3h#UkMT~p7jq6QeA|V-WWxiY|Ky@G_u+0BWut7p6Zbemm`Rby)Nw`cs zos=<-Fw7>kQHUj3$?M$B@kw4CS;A*1eoZ@UQ|DU8cslVv#i_iv9KTXTw@pi_qJ zxNBFTHv#?;oLjG`z;nT2eN3!nytKaYl8#^$E@OlHq`%Y3Mb1rG9fZg0R;#k&VDkCj zWP2qmpQmrC1z2bwz&Gc+-giVMCdrRKza0;kg~B=}Vnze}!6*iy{Ht?fNAnkruV`O9 z9&=9aa!PZ&5s|4R%Sr=YM?A2NySbl_Wb{_okr{VV-fwKVN;cSrVeMuw#@ z=kP?7Uv{ue5!YjUx&q|9_lLC$#P!U5HrV2?+x!dAODpUab7I4UT)G+Exb=`gWxSkn ziZv=t31jx0mim%2UP*wf3c7EpkznVYFI9awPkl<)Cx!6VRMu*Z(KO_X47O7*)eDE0 zFv|+Rgak$TZcLcI$7$VOW>imZB`oI{1SI_<_-B~nKGEcR4ZaF>#e9?tEFOn*ABQLX zuN$oXkZft~w`A65diR~nz0>4DOA|x1tFJ+NtlJ;C7-8$qrGCFvicj+F)HA|tn&OgV zt~{Tt4*vluMhQkdXjz?!)0`iZSx5C3(Rq6)ALdmC0{yY{RL7VFmB^^tLxiNM2}*X- zh1aG`p+Hl39Nw70NIaoxU788MWqKClM^r7B-?FTa%^e*EtpzFto?qAAUW_v_T5VfUqOCg| zyaX{3st@}$75FcjKcmN{gHa(`!_3W2Oq30hu=Tz|m(x`a!SgPK+W2YEQX`2#XHm9h zoerNlgNNu$_+BTLyQ=2lK>-CpbG60!N?$}{ev%0FP^_)T3Fc(*l$p7Ck0p7;hvABm zAB&3QFbfk?cFJq&)gWhxbgjHGGpTelTwTu)OE_-=xO}8GX9~gqY#b&e+Lk*4JJ|67^^4y(B%5whbW3L_gkQR?X}!tX1UY+q_LcLanl#NPV$SOqb+=O+ z(Atxuhb3(OZFe+U^$TCF-VUUt#xvhj9OyyXj@=$DqFh_%-cbk-z-{%6TGkK<`wI~fn zRU-Xt4lA}tsYq+Sem9GzR1`_g}mKmbnpY)weIy^Sy;Z=+!m%$ zZouJGZMsOKVViX}<>3fnFRmuk$5DzGSK=_u`P;lVB4)XySzM%{W&a&9ilgNwTCnun za$@CdLNQfUAle8v+vl487FK3L9haN2*Y2naYr2hRhpB%V*pHa^A@l%)QZZ=;*aAC@lbMuqYP{r3my7~X)6S-Rl+%@DSuUi|u zzrcRpN!nR*{2QrlE%--DEihmsB}MWJ{^s)=91Fiv6BXGy|BY!vP9&6dIp$IIH-mep z1iJ4|CbwH>1%>LBzp7($?LLmP41^|4W>5;hh1#0em;KVo|5kU_3wcnf3}u24hzGZk zB)=*Xi5P7tVaA&=Xq*_f6>Qg|1|PN%A!FmP@U;%wY_NZt7xHS`(`_QbW_=%-dG-_N}ouBId&49h;9j zVZ`qfEG0Z_gf%M9>v(a7;#4FRGaJN_jj+MN!QO4j@izF9S{PoqXv6*c1o{FO4kP6M z2-WJ*eEp5OnJgLLpWLmQk)-vusoq<^=eJj=0&f|K?neV$f}4%$Y0RkiO1$(yhzLa^ zC2Hfy9-o06x<}kHJd(E_(>p5y98|;PGWt{jjjll>oOy^$@-Ulbnq@4Ux#dw1Y}oua ziu4gQ2UK9qi)=?a93cwR(6xD)JCxmhPi>40Te{sNDzD|TlBDl=#CLAOpC5K@K#Fh@ zH&ct4c0{FHePp}s2+PTFwNTF-F^d-_7(x+*hNosli!xd=kQEp9rmq%)oYsC`SPz%V zoTpXx6w-u>Ww%RpnB0#hjE?*tOEYr2+TEIm_*>QPf)OcM>399vV?MnkKx`Z2LQk!L zsuoy0WeI}yJbo^Nrp{=chc6=r5S)L^iu_yZi-k0kPA=%sb&2dc#3H5Z<0y=5-TH9z zV_fO1dBssX(QGK~C`z8Wcytvs$m?x11d59|w8S8txb3dZ3T=;cZX~~1(&4M-= zi_NCH;RoVa@zfi48e8W)K9nN$af-F6dl#-iEIs}GMH}MtZop)1$AN)Q#uf^R)yGJ8 zGANYVrco{Fsi#0i z-16Vi5y#w=n@_3)+H{u*KQvuS2!~E515HtKe=9THnvyft-S9gtDjmf&Pgo{CV}fXU z-K*@i**}Zvp1tcz9}_4YB1R@}yK?>Sty&divL%}yK5DYlY9qdjHlD_85Nvr%Q48W1 z>}+ji&lp(tE2b=kfnpHkd5nX}9t{l*4;Q!01mP-I(2Y??jGSS;P?1IOMh2c@w4O<; z=9HNlD28~YLGbrIPzjYKY^vOzNoXRg1Fd(Y$ELqJx7=MrjC3$-VK~hqqnA^*UU@qD zTT2t!=tuZcdWs8MZOqZL zQdD~!MZDvi2WFuU2syyBmFs&hXPr2@5d8^TT(z2AOoatx%Dg3tyQNkt)$o6)EDSiIY^OzfiLF(Rew@fIPz!6uf(QXnm>uPSqy705I0mxYlQWk+b}- zr1yUS9oahR$2wXMq$t_h*+5m92nFzNNeH7YkNep8_!I(glzxnB5oeUwH$J zFu2D!Am9ZU7FWTY1$*s>zDw^8uq>V&AG3l#EG$oBlaFI~6k0Z!Y0m)UU3tE4R_JZu ztm+0A=2h*s0w^6RwW?a+X=MSE_V0RYV1>Jz4Dh#?)15l72}1{RVbS}kIDZhIn3pS8 zm-K${KP(mDFHPss%qIjd$pmZrC!j$(Kk{OCGFQ~Et)5a2tkeKluGLXb3L};V2@v;t zg|Cd0D)O1O+DU3=#OmYtDs$WqU!*;fig~SXcBvMtXC$kDB=R8ES@?8WSW(fod<-<9 zse12H7!KzkkU5}XdktWDl}iFSZB}1Q3`&uLPO6IVSwC1%u?Zu0rkEl(UgH=68D^in zZCESQ(*}xnUO$t7%TB7Ez1O~ik@r{vfzWZ+%X1tEW$E?{f=0H`xdy9v`=P;!dN&lB zddzr)%a>JP6#=1_PxB5>^C0E(taUwGm~MW%3c+%pq@;O zLC=%O_v-6nd?G*KdjaAIt^=V;Z_!11tE!v){^{u{5DRBmUqqK)b8|8hk==H`Yk}ka zXQ!fbJ1s7A!VI_C80in5W)Sa_-ra2`vrUd24ahDJuuYj6@ekfYjIJPUqy|*2@@H*y zy4nI+c7{ImV;vP0dJY{te3#{hxl%7lVs}{Ux?L@5J~v=R1nqM3BM&<|Ti^l|{-FL^ zs>IN9lIS)yLqF>5;BW$1n%(WY^U(oZOPj6VTSxX_5eAD+1@GKcQw6X{8{JQ*S^NEl zz5Peh^4HP(iu!zRx3yKEb@1$r;|Q-~PYUGa%Q}CnED!1z{4~yCV|~aQd3G&!HxBMB z*w({>XF%Y?{pCsM5hk(ukONfOD_1u(^YZ7+MeN4q^F$WHgv~!RlDMe8_1_>@f2Q;V z?AD8%v9Z0FG~Bx&+4Jh3v)w|wZfOIX{6pu-L+7ifLII7Wm0kxAJFlk!tSiaI3Mr%?^gDU{g>n7G!{Wj8btAhe znrK&^e$<(LE%n&4WQ~bcWmN>>TLF&4EAOk+QVN?~GB1B8C&DwO8~reWUGt1GMyC;k zQf!+mN}Dct@c8E{xD_{=l&*u;J5s^*J*|up0@?P8XrPJgB^nlr9c{P5@k=zpq&@QC%wnJojZ^h08(LW*CFwLi4uL{a(5sQY z4?g^w(pN5rndjiV779I%0!^QmIz|Ya=JvVz^CyUwngfK6pO7^&ucEs;DR%77Nm4Pd z4UcKGr`TC8JcSmD%zsbH``^N@Mx~pWrLBL9e#;l~$!R zK9o{(7{Sltg%cS5N7N$j(xis0^;P@_gSW7|;#Ne?z~1P{0B)tp?;YwFY{|mNEy@XRs$1}41)tn6lTpiVtgBUw#R}#yp5PO_s&e1GHl~ECyEP~DR{EU^ z;YP4OeP!;gRh_85_o{WvfQ-G{FDaN62;I+boC7lRavc1Ja=iORa`%wTOS%9QCRza* zslc(34evE=Gv@&0&;elssVG7PF3?ZsXlk3BH7%Pi?on2is%yn>ogoyd%&Y&GI>@%7 z1|H%#S#o8ZfH3*h@)%?0Y4}L=>~Gh0NQSYioyndBcYR#fhJECS+BMK`JBo zLK&aAth1PajZ{<;>DJp(8yqlJ^sYO#q-bU*wO9vz8$(6^F%r?WkZ+wKjyriL#hoP2 zBgjhYH2%ObLrXVAP_(ThRCV?*Mvxd$gFy7zj6b;kF_ZHtLx;!rriqFgMwpYAC>Qw^ z*NZ3!JDM4(iKkvkCSB1e`oJN_mS@gqjgjy);_MJq3A13yU6dG3q={ONUacg=dAGI} zxMyy+$o!9^8`j~$oYy)s?DO%WycZFpg7vg8Bqc9BEApE@F>{M8mJ<_lvGAnL_LO{Z zIbPpBN>gx!!3Ebs0FsRyA&VEQPWL;9k|H%nH?Gjkk*dgQ(jGyqvNIO>B6%_{=~hL~ z%d*w8txM>ma9Z4JI~nfDe`Tl%>X@#= znQ7&&SBJDpP4%o=jLhz1S7w1oJ;R^9&LrToI6Nh-Nandl|4S*0&|FoWenWZ6@xiWw zmoo!u&u^=$uo*rv$#Zy#*pQKzSwQY1J+uY^1LcM}6rwB{Xa`e347IN;+TRtFe!^px zvfA^_M@(L!>%l@Mt`sq~6!BtcTv_b$&H-GtwSif>89 z(N0DF>nAqp`{a8Rlz9e4+t1(^Iq{r0NmPxC>&e^SMpc82{c4mV*cxiXOj430& zOqvrS*{8cpNbtY`m)`phcIuC8PZOe+>VgYQtw2t+X>55zGGgs=;ry~UdFu1BR=uE9e-wb6|lJNiK ze`cvL8?EBl3f@(soAbg(qNQdLZs*r^MARZaNA-}wn3J9!MQr@8g`V0 zKExwL9!_TtR)P_*AXy6D#2aFsefcjz?^xCGgw;T<5+(cn7ehH_{hOa=x4Juw(^@-f ze##fgvZ8UGBWs`Jq$J0Hy}t;YOUPtS+Sxea{oDp%1#Hhk6| zc*}n}LeSIi8<@?WircBa(vFvM^1-(lEzYIQBP*1o=BURa{e6dJTU@M*$l&~FeXI5R z?#x!}oyw#|1G`yaPf-doYe$v)BD~gSlMWN%ogz& zMp(j0Z!w0D1=K}im2?IRB~CUV3(3=ukHQxiO43Y)8AS2_2_cqNP06xPh}IR{5ciFo z!zwV@hNEifn1=fk>C?dH98-A~n)!CcCSx8U7UNfhi#f>ND%$Jb&AKq<%bNeN!cqA< z1%OUq|MZ4?RNG^j9p1$rOK~Sh32m3C^LPGf{ZbS&h(G+!bk6clQwJ}3s@%a{eF8`zbKB-8cTFlT7(4 z{#N8P$D9+Hl{*Uv7I}X0k_~{0BvQ;k{KWbz4%sF>J6l9hkPsOT460RfeT!3`ZiY7t zJP^owsi-KF{j{+?^g?V*TU}Bw;mS!-1jR#-RyQ|xs;nlTuIZdh^q4$U{JRq~-T&7D zEas{gxDz`GLrL{N6+@D1>L|GK3+!}Kjn!hkwmG<|GurFwPDivlIbE_5N3&bN%)B@=NwrCjBV@;d)YwyJP`S?Ib(fgXoi3yOzU7g) zQXtmQ(6w(3h)0fp#m^m@|LZK7Zy?UBYr$UybNQ<$WeX`{0u>F-Yvf_|#XoW<$(kjT zwBuWJAwY}4C>m};!j(IsZluO!8cp_m`t^&5Pcc>W7Uu2US9JTIM?XNm`wq~!71e@H zWL2JL0Tu<5CTl>8uo-neI=oyv$QEi=N|7gO3GwrLt^z6m#MoQt02$V#)ou&;MOQ%U z5orH1lvAXF+Iyjrs324P8te_|C7@8uGcj1<>LbEp_noUWYywc2n3zh*@7`YV^@_25 z@8dY#g~Kb+%X~CQs*G!UkB~dL!>wJ;F3{J#D)hjBClc{ocwy=@>kj*jeDQ`6wGhCwK}7YMfe6V)saNIEc&1{k(D=2uhzB_crF zj3V*Pn*VzOfhXr!bjnp#Rf@j5+?+*GCCR`k#i*6Zo>6=aM2sMEJW*)2eF6MRjn*H^ zi|pT_CfGMW&={T5)Yk!f4G2ZMJp!W&+0zsmq)#c~^5pgQ?Ij=ROc4_wp#FLXWdWpK z+N<@pg?;K_~u$|(pHBRm@?obKqzon%r{eT;r+K$u(K%*n- z4?1C(oXr5q=~6;64~at4eahPJH|I8^578HKaLg(U2A#ec&Dwf;OtCCa1`XkNC;3-# zz~A%V9#Pit#yoe#%8XHiDT z+n(dBgDvgtzQCm>wz=VWT3G?&Lze&h?_wfXu8?pLfu^!k**(L^#I-wl8Z7N|nHuuK zG{rzrdkY>JzZpJ14R|R3ih5{@5I`e(bUiF**n)&hKm?oHnj;cMWxQ=qz}}b%~f@p8>U7)KCJ0W%P7ot^ol7!07lM*Oi&uwsb~E zPfsI<;RIdWviU!dLd3!%LYF{_;t(nWY?G!pXZkzA!(`&`f8?=)C^jGr(%pcq!~?n9 z{HcwGJ%wK>^;>!?Q?~8Jv=a2pSnnN7{+0mEv-?RIG5}rZl(XJM`8zuPKDp`0NgvE; ztXr^eMv9j;I2%N*Ldzok6+P(a6U;qrVeye%$|ljn)zx*QAAz+bTuo3j?s^(td^Tdc znf~9oyvxT^4hDECr?&%Sq@>~qlM#{6MltwQscek+ok8Gx2xo1s1p4Do0p32v&UwtV zupb}QTD^s`Fw-yI`D<&WMRatku_mj9vjy7T{)P4aAAulQqKWO3(R!jeB#-o*oOud= z6Fj7V&b?YNI+5ExJ&mt=DWLc6$g#$wu~{}Gd^EJBXqK<#Rsy~}AX{&`N9GACFRTwP zTrkH-`O;+UpuMh`BacBC$`D!5Ef5Y1LrO9!f*xX63w~pu@p3TKK_UB8OBs>$gDy50 zwT?c^(KoLmT!zAwyF{}xMg724FqBEV|E!NoHdj$QU?*|K_4+o&I$LBRIE^@SEG{J_ zrBbUF=#kWK5E3*Xpt+i1)Y;xX7IzMN4d3QDT%UV=43UtGi|kiaFSRu}pL=Lu-G`44 zITcD$YL4l--*sY0)i%2-$TM~YwpoH;haAPr3w~mo=fiG`4ZtMa6U+RTdhviOga73JUM zwM0O}*xWOBQOG0;qWY@LqBcqM@RxBAi>pE~I;x^E+nG}4()z~7CtViY2;++Iu6bhE zk@^_lP&I!Ujerij$iZ1%TpYR3@0KtOKG&T75flNdYj-LN9@;HhfB{uBuUK*e3wuQI z*pLk7=?2|yQ%q`c?PQ?s-js%bu>ei-K6}QlSJ�o?kZaLmrkpZH!*WdIW@4op&0 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 {D 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"AssetRegistry")); - AssetRegistryModule.Get().SearchAllAssets(true); - } - - TSharedPtr ExportShaderPatchSetting = MakeShareable(new FExportShaderPatchSettings); - - FString JsonContent; - if (FPaths::FileExists(config_path) && FFileHelper::LoadFileToString(JsonContent, *config_path)) - { - THotPatcherTemplateHelper::TDeserializeJsonStringAsStruct(JsonContent,*ExportShaderPatchSetting); - } - - TMap KeyValues = THotPatcherTemplateHelper::GetCommandLineParamsMap(Params); - THotPatcherTemplateHelper::ReplaceProperty(*ExportShaderPatchSetting, KeyValues); - - FString FinalConfig; - THotPatcherTemplateHelper::TSerializeStructAsJsonString(*ExportShaderPatchSetting,FinalConfig); - UE_LOG(LogHotShaderPatchCommandlet, Display, TEXT("%s"), *FinalConfig); - - UShaderPatchProxy* ShaderPatchProxy = NewObject(); - ShaderPatchProxy->AddToRoot(); - ShaderPatchProxy->Init(ExportShaderPatchSetting.Get()); - bool bExportStatus = ShaderPatchProxy->DoExport(); - - UE_LOG(LogHotShaderPatchCommandlet,Display,TEXT("Export Shader Patch Misstion is %s!"),bExportStatus?TEXT("Successed"):TEXT("Failure")); - - if(FParse::Param(FCommandLine::Get(), TEXT("wait"))) - { - system("pause"); - } - - return bExportStatus ? 0 : -1; -} diff --git a/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotShaderPatchCommandlet.h b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotShaderPatchCommandlet.h deleted file mode 100644 index 2ef5866e..00000000 --- a/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotShaderPatchCommandlet.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "HotPatcherCommandletBase.h" -#include "Commandlets/Commandlet.h" -#include "HotShaderPatchCommandlet.generated.h" - -DECLARE_LOG_CATEGORY_EXTERN(LogHotShaderPatchCommandlet, All, All); - -UCLASS() -class UHotShaderPatchCommandlet :public UHotPatcherCommandletBase -{ - GENERATED_BODY() - -public: - - virtual int32 Main(const FString& Params)override; -}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/HotPatcherCore.Build.cs b/HotPatcher/Source/HotPatcherCore/HotPatcherCore.Build.cs index edfe51f9..5538cf65 100644 --- a/HotPatcher/Source/HotPatcherCore/HotPatcherCore.Build.cs +++ b/HotPatcher/Source/HotPatcherCore/HotPatcherCore.Build.cs @@ -61,7 +61,6 @@ public HotPatcherCore(ReadOnlyTargetRules Target) : base(Target) "InputCore", "CoreUObject", "Engine", - "RenderCore", "Sockets" // ... add private dependencies that you statically link with here ... } diff --git a/HotPatcher/Source/HotPatcherCore/Private/Cooker/MultiCooker/FlibHotCookerHelper.cpp b/HotPatcher/Source/HotPatcherCore/Private/Cooker/MultiCooker/FlibHotCookerHelper.cpp index d97eb794..f4499c11 100644 --- a/HotPatcher/Source/HotPatcherCore/Private/Cooker/MultiCooker/FlibHotCookerHelper.cpp +++ b/HotPatcher/Source/HotPatcherCore/Private/Cooker/MultiCooker/FlibHotCookerHelper.cpp @@ -7,7 +7,7 @@ #include "FlibPatchParserHelper.h" #include "Cooker/MultiCooker/FCookShaderCollectionProxy.h" #include "Interfaces/IPluginManager.h" -#include "ShaderPatch/FlibShaderCodeLibraryHelper.h" +#include "ShaderLibUtils/FlibShaderCodeLibraryHelper.h" // FString UFlibHotCookerHelper::GetMultiCookerBaseDir() // { diff --git a/HotPatcher/Source/HotPatcherCore/Private/Cooker/MultiCooker/SingleCookerProxy.cpp b/HotPatcher/Source/HotPatcherCore/Private/Cooker/MultiCooker/SingleCookerProxy.cpp index 168370fb..f1343434 100644 --- a/HotPatcher/Source/HotPatcherCore/Private/Cooker/MultiCooker/SingleCookerProxy.cpp +++ b/HotPatcher/Source/HotPatcherCore/Private/Cooker/MultiCooker/SingleCookerProxy.cpp @@ -8,7 +8,7 @@ #include "UObject/UObjectHash.h" #include "UObject/UObjectIterator.h" #include "Async/ParallelFor.h" -#include "ShaderPatch/FlibShaderCodeLibraryHelper.h" +#include "ShaderLibUtils/FlibShaderCodeLibraryHelper.h" #include "ThreadUtils/FThreadUtils.hpp" #include "Cooker/MultiCooker/FCookShaderCollectionProxy.h" #include "Engine/Engine.h" diff --git a/HotPatcher/Source/HotPatcherCore/Private/CreatePatch/PatcherProxy.cpp b/HotPatcher/Source/HotPatcherCore/Private/CreatePatch/PatcherProxy.cpp index 4a51b5da..34127301 100644 --- a/HotPatcher/Source/HotPatcherCore/Private/CreatePatch/PatcherProxy.cpp +++ b/HotPatcher/Source/HotPatcherCore/Private/CreatePatch/PatcherProxy.cpp @@ -7,7 +7,7 @@ #include "CreatePatch/HotPatcherContext.h" #include "FlibHotPatcherCoreHelper.h" -#include "ShaderPatch/FlibShaderCodeLibraryHelper.h" +#include "ShaderLibUtils/FlibShaderCodeLibraryHelper.h" #include "Cooker/MultiCooker/FCookShaderCollectionProxy.h" // engine header diff --git a/HotPatcher/Source/HotPatcherCore/Private/FlibHotPatcherCoreHelper.cpp b/HotPatcher/Source/HotPatcherCore/Private/FlibHotPatcherCoreHelper.cpp index da63966d..440d86a7 100644 --- a/HotPatcher/Source/HotPatcherCore/Private/FlibHotPatcherCoreHelper.cpp +++ b/HotPatcher/Source/HotPatcherCore/Private/FlibHotPatcherCoreHelper.cpp @@ -5,7 +5,7 @@ #include "HotPatcherCore.h" #include "CreatePatch/FExportPatchSettings.h" #include "CreatePatch/FExportReleaseSettings.h" -#include "ShaderPatch/FlibShaderCodeLibraryHelper.h" +#include "ShaderLibUtils/FlibShaderCodeLibraryHelper.h" #include "ThreadUtils/FThreadUtils.hpp" // engine header diff --git a/HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/FCookShaderCollectionProxy.cpp b/HotPatcher/Source/HotPatcherCore/Private/ShaderLibUtils/FCookShaderCollectionProxy.cpp similarity index 96% rename from HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/FCookShaderCollectionProxy.cpp rename to HotPatcher/Source/HotPatcherCore/Private/ShaderLibUtils/FCookShaderCollectionProxy.cpp index ff73df46..f4484a30 100644 --- a/HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/FCookShaderCollectionProxy.cpp +++ b/HotPatcher/Source/HotPatcherCore/Private/ShaderLibUtils/FCookShaderCollectionProxy.cpp @@ -1,7 +1,7 @@ #include "Cooker/MultiCooker/FCookShaderCollectionProxy.h" #include "FlibHotPatcherCoreHelper.h" #include "Cooker/MultiCooker/FlibHotCookerHelper.h" -#include "ShaderPatch/FlibShaderCodeLibraryHelper.h" +#include "ShaderLibUtils//FlibShaderCodeLibraryHelper.h" #include "Interfaces/ITargetPlatform.h" #include "Resources/Version.h" diff --git a/HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/FlibShaderCodeLibraryHelper.cpp b/HotPatcher/Source/HotPatcherCore/Private/ShaderLibUtils/FlibShaderCodeLibraryHelper.cpp similarity index 94% rename from HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/FlibShaderCodeLibraryHelper.cpp rename to HotPatcher/Source/HotPatcherCore/Private/ShaderLibUtils/FlibShaderCodeLibraryHelper.cpp index f1136024..41fff6ee 100644 --- a/HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/FlibShaderCodeLibraryHelper.cpp +++ b/HotPatcher/Source/HotPatcherCore/Private/ShaderLibUtils/FlibShaderCodeLibraryHelper.cpp @@ -1,6 +1,6 @@ // Fill out your copyright notice in the Description page of Project Settings. -#include "ShaderPatch/FlibShaderCodeLibraryHelper.h" +#include "ShaderLibUtils//FlibShaderCodeLibraryHelper.h" #include "HotPatcherLog.h" #include "FlibHotPatcherCoreHelper.h" #include "HotPatcherCore.h" @@ -13,6 +13,10 @@ #define REMAPPED_PLUGINS TEXT("RemappedPlugins") +FString UFlibShaderCodeLibraryHelper::ShaderExtension = TEXT(".ushaderbytecode"); +FString UFlibShaderCodeLibraryHelper::ShaderAssetInfoExtension = TEXT(".assetinfo.json"); +FString UFlibShaderCodeLibraryHelper::StableExtension = TEXT(".scl.csv"); + // FMergeShaderCollectionProxy::FMergeShaderCollectionProxy(const TArray& InShaderCodeFiles):ShaderCodeFiles(InShaderCodeFiles) // { // Init(); diff --git a/HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/FlibShaderPatchHelper.cpp b/HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/FlibShaderPatchHelper.cpp deleted file mode 100644 index db0da2a0..00000000 --- a/HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/FlibShaderPatchHelper.cpp +++ /dev/null @@ -1,217 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - - -#include "ShaderPatch/FlibShaderPatchHelper.h" -#include "ETargetPlatform.h" -#include "FlibHotPatcherCoreHelper.h" -#include "Misc/Paths.h" -#include "ShaderCodeLibrary.h" -#include "Settings/ProjectPackagingSettings.h" - - -FString UFlibShaderPatchHelper::ShaderExtension = TEXT(".ushaderbytecode"); -FString UFlibShaderPatchHelper::ShaderAssetInfoExtension = TEXT(".assetinfo.json"); -FString UFlibShaderPatchHelper::StableExtension = TEXT(".scl.csv"); - -bool UFlibShaderPatchHelper::CreateShaderCodePatch(TArray const& OldMetaDataDirs, FString const& NewMetaDataDir, FString const& OutDir, bool bNativeFormat,bool bDeterministicShaderCodeOrder) -{ -#if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 26 - return FShaderLibraryCooker::CreatePatchLibrary(OldMetaDataDirs,NewMetaDataDir,OutDir,bNativeFormat,bDeterministicShaderCodeOrder); -#else - #if ENGINE_MINOR_VERSION > 25 - return FShaderCodeLibrary::CreatePatchLibrary(OldMetaDataDirs,NewMetaDataDir,OutDir,bNativeFormat,bDeterministicShaderCodeOrder); - #else - #if ENGINE_MINOR_VERSION > 23 - return FShaderCodeLibrary::CreatePatchLibrary(OldMetaDataDirs,NewMetaDataDir,OutDir,bNativeFormat); - #else - return false; - #endif - #endif -#endif -} - -TArray UFlibShaderPatchHelper::ConvDirectoryPathToStr(const TArray& Dirs) -{ - TArray result; - for(const auto& Dir:Dirs) - { - FString Path = FPaths::ConvertRelativePathToFull(Dir.Path); - if(FPaths::DirectoryExists(Path)) - { - result.AddUnique(Path); - } - } - return result; -} - -FString UFlibShaderPatchHelper::GetShaderStableInfoFileNameByShaderArchiveFileName(const FString& ShaderArchiveFileName) -{ - FString ShaderInfoFileName = ShaderArchiveFileName; - ShaderInfoFileName.RemoveFromStart(TEXT("ShaderArchive-")); - ShaderInfoFileName.RemoveFromEnd(TEXT(".ushaderbytecode")); - ShaderInfoFileName = FString::Printf(TEXT("ShaderStableInfo-%s.scl.csv"),*ShaderInfoFileName); - return ShaderInfoFileName; -} - -FString UFlibShaderPatchHelper::GetShaderInfoFileNameByShaderArchiveFileName(const FString& ShaderArchiveFileName) -{ - FString ShaderInfoFileName = ShaderArchiveFileName; - ShaderInfoFileName.RemoveFromStart(TEXT("ShaderArchive-")); - ShaderInfoFileName.RemoveFromEnd(TEXT(".ushaderbytecode")); - ShaderInfoFileName = FString::Printf(TEXT("ShaderAssetInfo-%s.assetinfo.json"),*ShaderInfoFileName); - return ShaderInfoFileName; -} - - - -// void UFlibShaderPatchHelper::InitShaderCodeLibrary(const TArray& Platforms) -// { -// const UProjectPackagingSettings* const PackagingSettings = GetDefault(); -// // IsUsingShaderCodeLibrary(); -// // CurrentCookMode == ECookMode::CookByTheBookFromTheEditor || CurrentCookMode == ECookMode::CookByTheBook; -// bool const bCacheShaderLibraries = true; -// if (bCacheShaderLibraries && PackagingSettings->bShareMaterialShaderCode) -// { -// FShaderCodeLibrary::InitForCooking(PackagingSettings->bSharedMaterialNativeLibraries); -// -// bool bAllPlatformsNeedStableKeys = false; -// // support setting without Hungarian prefix for the compatibility, but allow newer one to override -// GConfig->GetBool(TEXT("DevOptions.Shaders"), TEXT("NeedsShaderStableKeys"), bAllPlatformsNeedStableKeys, GEngineIni); -// GConfig->GetBool(TEXT("DevOptions.Shaders"), TEXT("bNeedsShaderStableKeys"), bAllPlatformsNeedStableKeys, GEngineIni); -// -// for (const ITargetPlatform* TargetPlatform : UFlibHotPatcherCoreHelper::GetTargetPlatformsByNames(Platforms)) -// { -// // Find out if this platform requires stable shader keys, by reading the platform setting file. -// bool bNeedShaderStableKeys = bAllPlatformsNeedStableKeys; -// FConfigFile PlatformIniFile; -// FConfigCacheIni::LoadLocalIniFile(PlatformIniFile, TEXT("Engine"), true, *TargetPlatform->IniPlatformName()); -// PlatformIniFile.GetBool(TEXT("DevOptions.Shaders"), TEXT("NeedsShaderStableKeys"), bNeedShaderStableKeys); -// PlatformIniFile.GetBool(TEXT("DevOptions.Shaders"), TEXT("bNeedsShaderStableKeys"), bNeedShaderStableKeys); -// -// bool bNeedsDeterministicOrder = PackagingSettings->bDeterministicShaderCodeOrder; -// FConfigFile PlatformGameIniFile; -// FConfigCacheIni::LoadLocalIniFile(PlatformGameIniFile, TEXT("Game"), true, *TargetPlatform->IniPlatformName()); -// PlatformGameIniFile.GetBool(TEXT("/Script/UnrealEd.ProjectPackagingSettings"), TEXT("bDeterministicShaderCodeOrder"), bNeedsDeterministicOrder); -// -// TArray ShaderFormats; -// TargetPlatform->GetAllTargetedShaderFormats(ShaderFormats); -// TArray ShaderFormatsWithStableKeys; -// for (FName& Format : ShaderFormats) -// { -// FShaderCodeLibrary::FShaderFormatDescriptor NewDesc; -// NewDesc.ShaderFormat = Format; -// NewDesc.bNeedsStableKeys = bNeedShaderStableKeys; -// NewDesc.bNeedsDeterministicOrder = bNeedsDeterministicOrder; -// ShaderFormatsWithStableKeys.Push(NewDesc); -// } -// -// if (ShaderFormats.Num() > 0) -// { -// FShaderCodeLibrary::CookShaderFormats(ShaderFormatsWithStableKeys); -// } -// } -// } -// } - - -// void UFlibShaderPatchHelper::CleanShaderCodeLibraries(const TArray& Platforms) -// { -// const UProjectPackagingSettings* const PackagingSettings = GetDefault(); -// bool const bCacheShaderLibraries = true;// IsUsingShaderCodeLibrary(); -// ITargetPlatformManagerModule& TPM = GetTargetPlatformManagerRef(); -// bool bIterativeCook = true;// IsCookFlagSet(ECookInitializationFlags::Iterative) || PackageDatas->GetNumCooked() != 0; -// -// // If not iterative then clean up our temporary files -// if (bCacheShaderLibraries && PackagingSettings->bShareMaterialShaderCode && !bIterativeCook) -// { -// for (const ITargetPlatform* TargetPlatform : UFlibHotPatcherCoreHelper::GetTargetPlatformsByNames(Platforms)) -// { -// TArray ShaderFormats; -// TargetPlatform->GetAllTargetedShaderFormats(ShaderFormats); -// if (ShaderFormats.Num() > 0) -// { -// FShaderCodeLibrary::CleanDirectories(ShaderFormats); -// } -// } -// } -// } -// -// FString ConvertToFullSandboxPath( const FString &FileName, bool bForWrite, const FString& PlatformName ) -// { -// FString Result = FPaths::ConvertRelativePathToFull(FileName);//ConvertToFullSandboxPath( FileName, bForWrite ); -// Result.ReplaceInline(TEXT("[Platform]"), *PlatformName); -// return Result; -// } -// -// static FString GenerateShaderCodeLibraryName(FString const& Name, bool bIsIterateSharedBuild) -// { -// FString ActualName = (!bIsIterateSharedBuild) ? Name : Name + TEXT("_SC"); -// return ActualName; -// } - -// void UFlibShaderPatchHelper::SaveShaderLibrary(const ITargetPlatform* TargetPlatform, FString const& Name, const TArray>* ChunkAssignments) -// { -// bool bIsIterateSharedBuild = true; // IsCookFlagSet(ECookInitializationFlags::IterateSharedBuild) -// FString ActualName = GenerateShaderCodeLibraryName(Name, bIsIterateSharedBuild); -// FString BasePath = FPaths::ProjectContentDir(); -// -// FString ShaderCodeDir = ConvertToFullSandboxPath(*BasePath, true, TargetPlatform->PlatformName()); -// -// const FString RootMetaDataPath = FPaths::ProjectDir() / TEXT("Metadata") / TEXT("PipelineCaches"); -// const FString MetaDataPathSB = FPaths::ConvertRelativePathToFull(*RootMetaDataPath); -// const FString MetaDataPath = MetaDataPathSB.Replace(TEXT("[Platform]"), *TargetPlatform->PlatformName()); -// -// // note that shader formats can be shared across the target platforms -// TArray ShaderFormats; -// TargetPlatform->GetAllTargetedShaderFormats(ShaderFormats); -// if (ShaderFormats.Num() > 0) -// { -// FString TargetPlatformName = TargetPlatform->PlatformName(); -// TMap> OutSCLCSVPaths; -// TArray& PlatformSCLCSVPaths = OutSCLCSVPaths.FindOrAdd(FName(TargetPlatformName)); -// bool bStoraged = FShaderCodeLibrary::SaveShaderCode(ShaderCodeDir, MetaDataPath, ShaderFormats, PlatformSCLCSVPaths, ChunkAssignments); -// -// if (UNLIKELY(!bStoraged)) -// { -// UE_LOG(LogTemp, Error, TEXT("%s"),*FString::Printf(TEXT("Saving shared material shader code library failed for %s."), *TargetPlatformName)); -// } -// else -// { -// const UProjectPackagingSettings* const PackagingSettings = GetDefault(); -// if (PackagingSettings->bSharedMaterialNativeLibraries) -// { -// bStoraged = FShaderCodeLibrary::PackageNativeShaderLibrary(ShaderCodeDir, ShaderFormats); -// if (!bStoraged) -// { -// // This is fatal - In this case we should cancel any launch on device operation or package write but we don't want to assert and crash the editor -// UE_LOG(LogTemp, Error, TEXT("%s"),*FString::Printf(TEXT("Package Native Shader Library failed for %s."), *TargetPlatformName)); -// } -// } -// for (const FString& Item : PlatformSCLCSVPaths) -// { -// UE_LOG(LogTemp, Display, TEXT("Saved scl.csv %s for platform %s, %d bytes"), *Item, *TargetPlatformName, -// IFileManager::Get().FileSize(*Item)); -// } -// } -// } -// } - -// void UFlibShaderPatchHelper::SaveGlobalShaderLibrary(const TArray& Platforms) -// { -// const TCHAR* GlobalShaderLibName = TEXT("Global"); -// bool bIsIterateSharedBuild = true; // IsCookFlagSet(ECookInitializationFlags::IterateSharedBuild) -// FString ActualName = GenerateShaderCodeLibraryName(GlobalShaderLibName, bIsIterateSharedBuild); -// -// const UProjectPackagingSettings* const PackagingSettings = GetDefault(); -// bool const bCacheShaderLibraries = true;//IsUsingShaderCodeLibrary(); -// if (bCacheShaderLibraries && PackagingSettings->bShareMaterialShaderCode) -// { -// // Save shader code map - cleaning directories is deliberately a separate loop here as we open the cache once per shader platform and we don't assume that they can't be shared across target platforms. -// for (const ITargetPlatform* TargetPlatform : UFlibHotPatcherCoreHelper::GetTargetPlatformsByNames(Platforms)) -// { -// SaveShaderLibrary(TargetPlatform, GlobalShaderLibName, nullptr); -// } -// -// FShaderCodeLibrary::CloseLibrary(ActualName); -// } -// } diff --git a/HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/ShaderPatchProxy.cpp b/HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/ShaderPatchProxy.cpp deleted file mode 100644 index 858b59f4..00000000 --- a/HotPatcher/Source/HotPatcherCore/Private/ShaderPatch/ShaderPatchProxy.cpp +++ /dev/null @@ -1,104 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. -#include "ShaderPatch/ShaderPatchProxy.h" -#include "FlibHotPatcherCoreHelper.h" -#include "Commandlets/HotShaderPatchCommandlet.h" -#include "ShaderPatch/FlibShaderPatchHelper.h" - -#define LOCTEXT_NAMESPACE "HotPatcherShaderPatchProxy" - -bool UShaderPatchProxy::DoExport() -{ - bool bStatus = false; - for(const auto& PlatformConfig:GetSettingObject()->ShaderPatchConfigs) - { - UE_LOG(LogHotShaderPatchCommandlet,Display,TEXT("Generating Shader Patch for %s"),*THotPatcherTemplateHelper::GetEnumNameByValue(PlatformConfig.Platform)); - - FString SaveToPath = FPaths::Combine(GetSettingObject()->GetSaveAbsPath(),GetSettingObject()->VersionID,THotPatcherTemplateHelper::GetEnumNameByValue(PlatformConfig.Platform)); - bool bCreateStatus = UFlibShaderPatchHelper::CreateShaderCodePatch( - UFlibShaderPatchHelper::ConvDirectoryPathToStr(PlatformConfig.OldMetadataDir), - FPaths::ConvertRelativePathToFull(PlatformConfig.NewMetadataDir.Path), - SaveToPath, - PlatformConfig.bNativeFormat, - PlatformConfig.bDeterministicShaderCodeOrder - ); - - auto GetShaderPatchFormatLambda = [](const FString& ShaderPatchDir)->TMap> - { - TMap> FormatLibraryMap; - TArray LibraryFiles; - IFileManager::Get().FindFiles(LibraryFiles, *(ShaderPatchDir), *UFlibShaderPatchHelper::ShaderExtension); - - for (FString const& Path : LibraryFiles) - { - FString Name = FPaths::GetBaseFilename(Path); - if (Name.RemoveFromStart(TEXT("ShaderArchive-"))) - { - TArray Components; - if (Name.ParseIntoArray(Components, TEXT("-")) == 2) - { - FName Format(*Components[1]); - TSet& Libraries = FormatLibraryMap.FindOrAdd(Format); - Libraries.Add(Components[0]); - } - } - } - return FormatLibraryMap; - }; - - if(bCreateStatus) - { - TMap> ShaderFormatLibraryMap = GetShaderPatchFormatLambda(SaveToPath); - FText Msg = LOCTEXT("GeneratedShaderPatch", "Successd to Generated the Shader Patch."); - TArray FormatNames; - ShaderFormatLibraryMap.GetKeys(FormatNames); - for(const auto& FormatName:FormatNames) - { - TArray LibraryNames= ShaderFormatLibraryMap[FormatName].Array(); - for(const auto& LibrartName:LibraryNames) - { - FString OutputFilePath = UFlibShaderPatchHelper::GetCodeArchiveFilename(SaveToPath, LibrartName, FormatName); - if(FPaths::FileExists(OutputFilePath)) - { - bStatus = true; - if(!IsRunningCommandlet()) - { - FHotPatcherDelegates::Get().GetNotifyFileGenerated().Broadcast(Msg, OutputFilePath); - } - else - { - UE_LOG(LogHotShaderPatchCommandlet,Display,TEXT("%s"),*Msg.ToString()); - } - } - else - { - UE_LOG(LogHotShaderPatchCommandlet,Display,TEXT("ERROR: %s not found!"),*OutputFilePath); - } - } - } - } - } - - if(GetSettingObject()->bStorageConfig) - { - FString SerializedJsonStr; - THotPatcherTemplateHelper::TSerializeStructAsJsonString(*GetSettingObject(),SerializedJsonStr); - - FString SaveToPath = FPaths::Combine(GetSettingObject()->GetSaveAbsPath(),GetSettingObject()->VersionID,GetSettingObject()->VersionID).Append(TEXT(".json")); - - if (FFileHelper::SaveStringToFile(SerializedJsonStr, *SaveToPath)) - { - FText Msg = LOCTEXT("SavedShaderPatchConfigMas", "Successd to Export the Shader Patch Config."); - if(!IsRunningCommandlet()) - { - FHotPatcherDelegates::Get().GetNotifyFileGenerated().Broadcast(Msg, SaveToPath); - } - else - { - UE_LOG(LogHotShaderPatchCommandlet,Display,TEXT("%s"),*Msg.ToString()); - } - } - } - return bStatus; -} - -#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Public/ShaderPatch/FlibShaderCodeLibraryHelper.h b/HotPatcher/Source/HotPatcherCore/Public/ShaderLibUtils/FlibShaderCodeLibraryHelper.h similarity index 72% rename from HotPatcher/Source/HotPatcherCore/Public/ShaderPatch/FlibShaderCodeLibraryHelper.h rename to HotPatcher/Source/HotPatcherCore/Public/ShaderLibUtils/FlibShaderCodeLibraryHelper.h index 54345cc8..211aea1c 100644 --- a/HotPatcher/Source/HotPatcherCore/Public/ShaderPatch/FlibShaderCodeLibraryHelper.h +++ b/HotPatcher/Source/HotPatcherCore/Public/ShaderLibUtils/FlibShaderCodeLibraryHelper.h @@ -61,4 +61,23 @@ class HOTPATCHERCORE_API UFlibShaderCodeLibraryHelper : public UBlueprintFunctio static void CancelMaterialShaderCompile(UMaterialInterface* MaterialInterface); static void CleanShaderWorkerDir(); + + static FString ShaderExtension; + static FString ShaderAssetInfoExtension; + static FString StableExtension; + + FORCEINLINE static FString GetCodeArchiveFilename(const FString& BaseDir, const FString& LibraryName, FName Platform) + { + return BaseDir / FString::Printf(TEXT("ShaderArchive-%s-"), *LibraryName) + Platform.ToString() + ShaderExtension; + } + FORCEINLINE static FString GetShaderAssetInfoFilename(const FString& BaseDir, const FString& LibraryName, FName Platform) + { + return BaseDir / FString::Printf(TEXT("ShaderAssetInfo-%s-"), *LibraryName) + Platform.ToString() + ShaderAssetInfoExtension; + } + + FORCEINLINE static FString GetStableInfoArchiveFilename(const FString& BaseDir, const FString& LibraryName, FName Platform) + { + return BaseDir / FString::Printf(TEXT("ShaderStableInfo-%s-"), *LibraryName) + Platform.ToString() + StableExtension; + } + }; diff --git a/HotPatcher/Source/HotPatcherCore/Public/ShaderPatch/FExportShaderPatchSettings.h b/HotPatcher/Source/HotPatcherCore/Public/ShaderPatch/FExportShaderPatchSettings.h deleted file mode 100644 index d447faf5..00000000 --- a/HotPatcher/Source/HotPatcherCore/Public/ShaderPatch/FExportShaderPatchSettings.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include "HotPatcherLog.h" -#include "CreatePatch/HotPatcherSettingBase.h" -#include "FlibPatchParserHelper.h" -#include "HotPatcherLog.h" -#include "ETargetPlatform.h" - -// engine header -#include "Misc/FileHelper.h" -#include "CoreMinimal.h" - -#include "UObject/ObjectMacros.h" -#include "UObject/Object.h" -#include "Engine/EngineTypes.h" -#include "Kismet/KismetStringLibrary.h" -#include "Serialization/JsonSerializer.h" -#include "Serialization/JsonWriter.h" - -#include "FExportShaderPatchSettings.generated.h" - - -USTRUCT(BlueprintType) -struct FShaderPatchConf -{ - GENERATED_USTRUCT_BODY() - - UPROPERTY(EditAnywhere, BlueprintReadWrite) - ETargetPlatform Platform = ETargetPlatform::None; - UPROPERTY(EditAnywhere, BlueprintReadWrite) - TArray OldMetadataDir; - UPROPERTY(EditAnywhere, BlueprintReadWrite) - FDirectoryPath NewMetadataDir; - UPROPERTY(EditAnywhere, BlueprintReadWrite) - bool bNativeFormat = false; - // since UE 4.26 (Below 4.26 this parameter has no effect) - UPROPERTY(EditAnywhere, BlueprintReadWrite) - bool bDeterministicShaderCodeOrder = true; -}; - -/** Singleton wrapper to allow for using the setting structure in SSettingsView */ -USTRUCT(BlueprintType) -struct HOTPATCHERCORE_API FExportShaderPatchSettings:public FHotPatcherSettingBase -{ - GENERATED_USTRUCT_BODY() -public: - FExportShaderPatchSettings(){} - virtual ~FExportShaderPatchSettings(){}; - - FORCEINLINE static FExportShaderPatchSettings* Get() - { - static FExportShaderPatchSettings StaticIns; - - return &StaticIns; - } - - UPROPERTY(EditAnywhere, Category="Version") - FString VersionID; - - UPROPERTY(EditAnywhere, Category="Config") - TArray ShaderPatchConfigs; - -}; - - diff --git a/HotPatcher/Source/HotPatcherCore/Public/ShaderPatch/FlibShaderPatchHelper.h b/HotPatcher/Source/HotPatcherCore/Public/ShaderPatch/FlibShaderPatchHelper.h deleted file mode 100644 index 1afea619..00000000 --- a/HotPatcher/Source/HotPatcherCore/Public/ShaderPatch/FlibShaderPatchHelper.h +++ /dev/null @@ -1,55 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "Engine/EngineTypes.h" -#include "CoreMinimal.h" - -#include "ETargetPlatform.h" -#include "Kismet/BlueprintFunctionLibrary.h" -#include "FlibShaderPatchHelper.generated.h" - - - -/** - * - */ -UCLASS() -class HOTPATCHERCORE_API UFlibShaderPatchHelper : public UBlueprintFunctionLibrary -{ - GENERATED_BODY() -public: - - UFUNCTION(BlueprintCallable) - static bool CreateShaderCodePatch(TArray const& OldMetaDataDirs, FString const& NewMetaDataDir, FString const& OutDir, bool bNativeFormat,bool bDeterministicShaderCodeOrder = true); - - UFUNCTION(BlueprintCallable) - static TArray ConvDirectoryPathToStr(const TArray& Dirs); - - static FString ShaderExtension; - static FString ShaderAssetInfoExtension; - static FString StableExtension; - - FORCEINLINE static FString GetCodeArchiveFilename(const FString& BaseDir, const FString& LibraryName, FName Platform) - { - return BaseDir / FString::Printf(TEXT("ShaderArchive-%s-"), *LibraryName) + Platform.ToString() + ShaderExtension; - } - FORCEINLINE static FString GetShaderAssetInfoFilename(const FString& BaseDir, const FString& LibraryName, FName Platform) - { - return BaseDir / FString::Printf(TEXT("ShaderAssetInfo-%s-"), *LibraryName) + Platform.ToString() + ShaderAssetInfoExtension; - } - - FORCEINLINE static FString GetStableInfoArchiveFilename(const FString& BaseDir, const FString& LibraryName, FName Platform) - { - return BaseDir / FString::Printf(TEXT("ShaderStableInfo-%s-"), *LibraryName) + Platform.ToString() + StableExtension; - } - - static FString GetShaderStableInfoFileNameByShaderArchiveFileName(const FString& ShaderArchiveFileName); - static FString GetShaderInfoFileNameByShaderArchiveFileName(const FString& ShaderArchiveFileName); - - // static void InitShaderCodeLibrary(const TArray& Platforms); - // static void CleanShaderCodeLibraries(const TArray& Platforms); - // void SaveGlobalShaderLibrary(const TArray& Platforms); - // void SaveShaderLibrary(const ITargetPlatform* TargetPlatform, FString const& Name, const TArray>* ChunkAssignments); -}; - diff --git a/HotPatcher/Source/HotPatcherCore/Public/ShaderPatch/ShaderPatchProxy.h b/HotPatcher/Source/HotPatcherCore/Public/ShaderPatch/ShaderPatchProxy.h deleted file mode 100644 index 10e9d0d2..00000000 --- a/HotPatcher/Source/HotPatcherCore/Public/ShaderPatch/ShaderPatchProxy.h +++ /dev/null @@ -1,24 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "CoreMinimal.h" -#include "CreatePatch/HotPatcherProxyBase.h" -#include "ShaderPatch/FExportShaderPatchSettings.h" -#include "ShaderPatchProxy.generated.h" - -/** - * - */ -UCLASS() -class HOTPATCHERCORE_API UShaderPatchProxy : public UHotPatcherProxyBase -{ - GENERATED_BODY() -public: - virtual bool DoExport() override; - FORCEINLINE bool IsRunningCommandlet()const{return ::IsRunningCommandlet();} - FORCEINLINE virtual FExportShaderPatchSettings* GetSettingObject()override - { - return (FExportShaderPatchSettings*)Setting; - } -}; diff --git a/HotPatcher/Source/HotPatcherEditor/Private/Cooker/SCookersPage.cpp b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/SCookersPage.cpp index c3d01eb7..e6c3246d 100644 --- a/HotPatcher/Source/HotPatcherEditor/Private/Cooker/SCookersPage.cpp +++ b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/SCookersPage.cpp @@ -29,7 +29,7 @@ void SCookersPage::Construct(const FArguments& InArgs, TSharedPtr Action = Cooker.Value.RequestWidget(GetContext()); + if(Cooker.Value.RequestWidget) + { + TSharedRef Action = Cooker.Value.RequestWidget(GetContext()); - Widget->AddSlot().AutoHeight().Padding(0.0, 8.0, 0.0, 0.0) - [ - Action - ]; - ActionWidgetMap.Add(*Cooker.Key,Action); + Widget->AddSlot().AutoHeight().Padding(0.0, 8.0, 0.0, 0.0) + [ + Action + ]; + ActionWidgetMap.Add(*Cooker.Key,Action); + } } } ChildSlot diff --git a/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/SPatchersPage.cpp b/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/SPatchersPage.cpp index 3bdc3a13..440805aa 100644 --- a/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/SPatchersPage.cpp +++ b/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/SPatchersPage.cpp @@ -3,7 +3,6 @@ #include "CreatePatch/SPatchersPage.h" #include "CreatePatch/SHotPatcherPatchWidget.h" #include "CreatePatch/SHotPatcherReleaseWidget.h" -#include "ShaderPatch/SShaderPatchWidget.h" // engine header #include "HotPatcherEditor.h" @@ -30,7 +29,7 @@ void SPatchersPage::Construct(const FArguments& InArgs, TSharedPtr Action = Patcher.Value.RequestWidget(GetContext()); - Widget->AddSlot().AutoHeight().Padding(0.0, 8.0, 0.0, 0.0) - [ - Action - ]; - ActionWidgetMap.Add(*Patcher.Key,Action); + if(Patcher.Value.RequestWidget) + { + TSharedRef Action = Patcher.Value.RequestWidget(GetContext()); + Widget->AddSlot().AutoHeight().Padding(0.0, 8.0, 0.0, 0.0) + [ + Action + ]; + ActionWidgetMap.Add(*Patcher.Key,Action); + } } } diff --git a/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherActionManager.cpp b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherActionManager.cpp index 2d171cd3..0bc61d8a 100644 --- a/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherActionManager.cpp +++ b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherActionManager.cpp @@ -3,7 +3,6 @@ #include "HotPatcherCore.h" #include "CreatePatch/SHotPatcherPatchWidget.h" #include "CreatePatch/SHotPatcherReleaseWidget.h" -#include "ShaderPatch/SShaderPatchWidget.h" #include "Cooker/OriginalCooker/SOriginalCookWidget.h" #include "Kismet/KismetTextLibrary.h" @@ -15,8 +14,42 @@ FHotPatcherActionManager FHotPatcherActionManager::Manager; void FHotPatcherActionManager::Init() { - FVersionUpdaterManager::Get().RequestRemoveVersion(GRemoteVersionFile); + FVersionUpdaterManager::Get().ModCurrentVersionGetter = [this](const FString& ActionName)->float + { + float CurrentVersion = 0.f; + FHotPatcherModDesc ModDesc; + if(GetModDescByName(ActionName,ModDesc)) + { + CurrentVersion = ModDesc.CurrentVersion; + } + return CurrentVersion; + }; + + FVersionUpdaterManager::Get().ModIsActivteCallback = [this](const FString& ModName)->bool + { + return IsActiviteMod(*ModName); + }; + + FVersionUpdaterManager::Get().RequestLocalRegistedMods = [this]()->TArray + { + TArray result; + for(const auto& HotPatcherMod:ModsDesc) + { + FChildModDesc ChildModDesc; + ChildModDesc.ModName = HotPatcherMod.Value.ModName; + ChildModDesc.CurrentVersion = HotPatcherMod.Value.CurrentVersion; + ChildModDesc.bIsBuiltInMod = HotPatcherMod.Value.bIsBuiltInMod; + ChildModDesc.Description = HotPatcherMod.Value.Description; + ChildModDesc.URL = HotPatcherMod.Value.URL; + ChildModDesc.UpdateURL = HotPatcherMod.Value.UpdateURL; + result.Add(ChildModDesc); + } + return result; + }; + SetupDefaultActions(); + + FVersionUpdaterManager::Get().RequestRemoveVersion(GRemoteVersionFile); } void FHotPatcherActionManager::Shutdown() @@ -38,7 +71,15 @@ void FHotPatcherActionManager::RegisteHotPatcherAction(const FString& Category, { return R.Priority > L.Priority; }); + + FHotPatcherActionDesc Desc; + Desc.Category = Category; + Desc.ActionName = ActionName; + Desc.ToolTip = Action.ActionToolTip.Get().ToString(); + Desc.Priority = Action.Priority; + Desc.RequestWidgetPtr = Action.RequestWidget; + ActionsDesc.Add(ActionName,Desc); OnHotPatcherActionRegisted.Broadcast(Category,ActionName,Action); } @@ -48,6 +89,7 @@ void FHotPatcherActionManager::RegisteHotPatcherAction(const FHotPatcherActionDe FHotPatcherAction NewPatcherAction ( *NewAction.ActionName, + *NewAction.ModName, UKismetTextLibrary::Conv_StringToText(NewAction.ActionName), UKismetTextLibrary::Conv_StringToText(NewAction.ToolTip), FSlateIcon(), @@ -55,9 +97,28 @@ void FHotPatcherActionManager::RegisteHotPatcherAction(const FHotPatcherActionDe NewAction.RequestWidgetPtr, NewAction.Priority ); + FHotPatcherActionManager::Get().RegisteHotPatcherAction(NewAction.Category,NewAction.ActionName,NewPatcherAction); } +void FHotPatcherActionManager::RegisteHotPatcherMod(const FHotPatcherModDesc& ModDesc) +{ + for(const auto& ActionDesc:ModDesc.ModActions) + { + FHotPatcherActionManager::Get().RegisteHotPatcherAction(ActionDesc); + } + ModsDesc.Add(*ModDesc.ModName,ModDesc); +} + +void FHotPatcherActionManager::UnRegisteHotPatcherMod(const FHotPatcherModDesc& ModDesc) +{ + for(const auto& ActionDesc:ModDesc.ModActions) + { + FHotPatcherActionManager::Get().UnRegisteHotPatcherAction(ActionDesc.Category,ActionDesc.ActionName); + } + ModsDesc.Remove(*ModDesc.ModName); +} + void FHotPatcherActionManager::UnRegisteHotPatcherAction(const FString& Category, const FString& ActionName) { TMap& CategoryIns = HotPatcherActions.FindOrAdd(Category); @@ -67,6 +128,7 @@ void FHotPatcherActionManager::UnRegisteHotPatcherAction(const FString& Category Action = *CategoryIns.Find(ActionName); CategoryIns.Remove(ActionName); } + ActionsDesc.Remove(*ActionName); OnHotPatcherActionUnRegisted.Broadcast(Category,ActionName,Action); } @@ -88,6 +150,19 @@ FHotPatcherAction* FHotPatcherActionManager::GetTopActionByCategory(const FStrin return result; } +bool FHotPatcherActionManager::IsActiviteMod(const FString& ModName) +{ + return ModsDesc.Contains(*ModName); +} + +bool FHotPatcherActionManager::IsSupportEditorAction(FString ActionName) +{ + FHotPatcherActionDesc Desc; + bool bDescStatus = GetActionDescByName(ActionName,Desc); + + return IsActiveAction(ActionName) && (bDescStatus && Desc.RequestWidgetPtr); +} + bool FHotPatcherActionManager::IsActiveAction(FString ActionName) { if(FVersionUpdaterManager::Get().IsRequestFinished()) @@ -117,37 +192,60 @@ bool FHotPatcherActionManager::IsActiveAction(FString ActionName) return true; } +bool FHotPatcherActionManager::GetActionDescByName(const FString& ActionName, FHotPatcherActionDesc& Desc) +{ + bool bStatus = ActionsDesc.Contains(ActionName); + if(bStatus) + { + Desc = *ActionsDesc.Find(ActionName); + } + return bStatus; +}; + +bool FHotPatcherActionManager::GetModDescByName(const FString& ModName, FHotPatcherModDesc& ModDesc) +{ + bool bStatus = ModsDesc.Contains(*ModName); + if(bStatus) + { + ModDesc = *ModsDesc.Find(*ModName); + } + return bStatus; +} +#define HOTPATCHEER_CORE_MODENAME TEXT("HotPatcherCore") +#define CMDHANDLER_MODENAME TEXT("CmdHandler") + void FHotPatcherActionManager::SetupDefaultActions() { - TArray DefaultActions; - DefaultActions.Emplace( - TEXT("Patcher"),LOCTEXT("ByExportRelease", "ByRelease"), LOCTEXT("ExportReleaseActionHint", "Export Release ALL Asset Dependencies."), FSlateIcon(),nullptr, + TArray BuiltInMods; + + FHotPatcherModDesc HotPatcherCoreMod; + HotPatcherCoreMod.ModName = HOTPATCHEER_CORE_MODENAME; + HotPatcherCoreMod.bIsBuiltInMod = true; + HotPatcherCoreMod.CurrentVersion = 1.0; + HotPatcherCoreMod.ModActions.Emplace( + TEXT("Patcher"),HOTPATCHEER_CORE_MODENAME,TEXT("ByRelease"), TEXT("Export Release ALL Asset Dependencies."), CREATE_ACTION_WIDGET_LAMBDA(SHotPatcherReleaseWidget,TEXT("ByRelease")), 0 ); - DefaultActions.Emplace( - TEXT("Patcher"),LOCTEXT("ByCreatePatch", "ByPatch"), LOCTEXT("CreatePatchActionHint", "Create an Patch form Release version."), FSlateIcon(),nullptr, + HotPatcherCoreMod.ModActions.Emplace( + TEXT("Patcher"),HOTPATCHEER_CORE_MODENAME,TEXT("ByPatch"), TEXT("Create an Patch form Release version."), CREATE_ACTION_WIDGET_LAMBDA(SHotPatcherPatchWidget,TEXT("ByPatch")), 1 ); - DefaultActions.Emplace( - TEXT("Patcher"),LOCTEXT("ByShaderPatch", "ByShaderPatch"), LOCTEXT("CreateShaderPatchActionHint", "Create an Shader code Patch form Metadata."), FSlateIcon(),nullptr, - CREATE_ACTION_WIDGET_LAMBDA(SShaderPatchWidget,TEXT("ByShaderPatch")), - -1 - ); + #if ENABLE_ORIGINAL_COOKER - DefaultActions.Emplace( - TEXT("Cooker"),LOCTEXT("ByOriginal", "ByOriginal"),LOCTEXT("OriginalCookerActionHint", "Use single-process Cook Content(UE Default)"),FSlateIcon(),nullptr, + HotPatcherCoreMod.ModActions.Emplace( + TEXT("Cooker"),TEXT("HotPatcherCore"),HOTPATCHEER_CORE_MODENAME,TEXT("Use single-process Cook Content(UE Default)"), CREATE_ACTION_WIDGET_LAMBDA(SOriginalCookWidget,TEXT("ByOriginal")), 0 ); #endif - for(const auto& Action:DefaultActions) + BuiltInMods.Add(HotPatcherCoreMod); + + for(const auto& Mod:BuiltInMods) { - RegisteHotPatcherAction(Action.Category.ToString(),Action.ActionName.Get().ToString(),Action); + RegisteHotPatcherMod(Mod); } } - - #undef LOCTEXT_NAMESPACE diff --git a/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherModBaseModule.cpp b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherModBaseModule.cpp index e555d243..51c2cdc9 100644 --- a/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherModBaseModule.cpp +++ b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherModBaseModule.cpp @@ -2,17 +2,10 @@ void FHotPatcherModBaseModule::StartupModule() { - for(const auto& ActionDesc:GetModActions()) - { - RegistedActions.Add(ActionDesc); - FHotPatcherActionManager::Get().RegisteHotPatcherAction(ActionDesc); - } + FHotPatcherActionManager::Get().RegisteHotPatcherMod(GetModDesc()); } void FHotPatcherModBaseModule::ShutdownModule() { - for(const auto& ActionDesc:RegistedActions) - { - FHotPatcherActionManager::Get().UnRegisteHotPatcherAction(ActionDesc.Category,ActionDesc.ActionName); - } + FHotPatcherActionManager::Get().UnRegisteHotPatcherMod(GetModDesc()); } diff --git a/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherStyle.cpp b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherStyle.cpp index 31c547b4..4a6d8162 100644 --- a/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherStyle.cpp +++ b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherStyle.cpp @@ -45,9 +45,7 @@ TSharedRef< FSlateStyleSet > FHotPatcherStyle::Create() { TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet("HotPatcherStyle")); Style->SetContentRoot(IPluginManager::Get().FindPlugin("HotPatcher")->GetBaseDir() / TEXT("Resources")); - Style->Set("HotPatcher.PluginAction", new IMAGE_BRUSH(TEXT("ButtonIcon_40x"), Icon40x40)); - return Style; } diff --git a/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/FVersionUpdaterManager.cpp b/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/FVersionUpdaterManager.cpp index 4f850975..d9da69e9 100644 --- a/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/FVersionUpdaterManager.cpp +++ b/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/FVersionUpdaterManager.cpp @@ -5,6 +5,7 @@ #include "Interfaces/IHttpRequest.h" #include "Interfaces/IHttpResponse.h" #include "HttpModule.h" +#include "Kismet/KismetStringLibrary.h" #include "Serialization/JsonReader.h" #include "Serialization/JsonSerializer.h" @@ -103,6 +104,33 @@ void FVersionUpdaterManager::OnRequestComplete(FHttpRequestPtr RequestPtr, FHttp RemoteVersion.ActiveActions.Add(*Name,GetActionArray(*Actions,*Name)); } } + + const TArray>* ActionDescs = nullptr; + if(Actions && (*Actions)->TryGetArrayField(TEXT("ModsDesc"),ActionDescs)) + { + for(const TSharedPtr& ModDescJsonValue:*ActionDescs) + { + const TSharedPtr& ModDescJsonObject = ModDescJsonValue.Get()->AsObject(); + FChildModDesc ModDesc; + ModDescJsonObject->TryGetStringField(TEXT("ModName"),ModDesc.ModName); + + FString Version; + if(ModDescJsonObject->TryGetStringField(TEXT("Version"),Version)) + { + ModDesc.RemoteVersion = UKismetStringLibrary::Conv_StringToFloat(Version); + } + ModDescJsonObject->TryGetStringField(TEXT("Desc"),ModDesc.Description); + ModDescJsonObject->TryGetStringField(TEXT("URL"),ModDesc.URL); + ModDescJsonObject->TryGetStringField(TEXT("UpdateURL"),ModDesc.UpdateURL); + ModDescJsonObject->TryGetBoolField(TEXT("bIsBuiltInMod"),ModDesc.bIsBuiltInMod); + + if(ModCurrentVersionGetter) + { + ModDesc.CurrentVersion = ModCurrentVersionGetter(ModDesc.ModName); + } + RemoteVersion.ModsDesc.Add(*ModDesc.ModName,ModDesc); + } + } } Remotes.Add(*ToolName,RemoteVersion); } diff --git a/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/FVersionUpdaterManager.h b/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/FVersionUpdaterManager.h index 2c7c2196..365152d9 100644 --- a/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/FVersionUpdaterManager.h +++ b/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/FVersionUpdaterManager.h @@ -4,6 +4,17 @@ #include "Misc/RemoteConfigIni.h" #include "FCountServerlessWrapper.h" +struct FChildModDesc +{ + FString ModName; + float CurrentVersion = 0.f; + float RemoteVersion = 0.f; + FString Description; + FString URL; + FString UpdateURL; + bool bIsBuiltInMod = false; +}; + struct FRemteVersionDescrible { FRemteVersionDescrible()=default; @@ -16,9 +27,9 @@ struct FRemteVersionDescrible FString URL; bool b3rdMods; TMap> ActiveActions; + TMap ModsDesc; }; - struct FVersionUpdaterManager { static FVersionUpdaterManager& Get() @@ -40,4 +51,11 @@ struct FVersionUpdaterManager bool bRequestFinished = false; TArray> OnRequestFinishedCallback; TSharedPtr Counter; +public: + // get mod current version. + TFunction ModCurrentVersionGetter = nullptr; + // check is valid activite mod callback; + TFunction ModIsActivteCallback = nullptr; + + TFunction()> RequestLocalRegistedMods = nullptr; }; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/SVersionUpdaterWidget.cpp b/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/SVersionUpdaterWidget.cpp index 14657b4d..edea2d69 100644 --- a/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/SVersionUpdaterWidget.cpp +++ b/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/SVersionUpdaterWidget.cpp @@ -14,10 +14,108 @@ #include "VersionUpdaterStyle.h" #include "Interfaces/IHttpRequest.h" #include "Interfaces/IHttpResponse.h" +#include "Interfaces/IPluginManager.h" #define LOCTEXT_NAMESPACE "VersionUpdaterWidget" +void SChildModWidget::Construct(const FArguments& InArgs) +{ + CurrentVersion = InArgs._CurrentVersion.Get(); + ModName = InArgs._ModName.Get(); + Description = InArgs._Description.Get(); + RemoteVersion = InArgs._RemoteVersion.Get(); + URL = InArgs._URL.Get(); + UpdateURL = InArgs._UpdateURL.Get(); + bIsBuiltInMod = InArgs._bIsBuiltInMod.Get(); + + ChildSlot + [ + SAssignNew(HorizontalBox,SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(0.0f, 0.0f, 0.0f, 0.0f) + [ + SNew(STextBlock) + .Text_Lambda([this]()->FText + { + FString DisplayStr; + if(bIsBuiltInMod) + { + DisplayStr = FString::Printf(TEXT("%s (Built-in Mod)"),*GetModName()); + } + else + { + DisplayStr = FString::Printf(TEXT("%s v%.1f"),*GetModName(),GetCurrentVersion()); + } + return UKismetTextLibrary::Conv_StringToText(DisplayStr); + }) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(10.0f, 0.0f, 0.0f, 0.0f) + [ + SNew(SHyperlink) + .Text(UKismetTextLibrary::Conv_StringToText(Description)) + .OnNavigate_Lambda([this]() + { + if(!URL.IsEmpty()) + { + FPlatformProcess::LaunchURL(*URL, NULL, NULL); + } + }) + ] + ]; + + // Update Version + if(!bIsBuiltInMod && RemoteVersion > CurrentVersion) + { + FString LaunchURL = UpdateURL; + if(LaunchURL.IsEmpty()){ LaunchURL = URL;} + + if(!LaunchURL.IsEmpty()) + { + HorizontalBox->AddSlot() + .Padding(8.0f, 0.0f, 4.0f, 0.0f) + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(SBox) + .WidthOverride(12) + .HeightOverride(12) + [ + SNew(SImage) + .Image(FVersionUpdaterStyle::GetBrush("Updater.SpawnableIconOverlay")) + ] + ]; + + HorizontalBox->AddSlot() + .Padding(0.0f, 0.0f, 4.0f, 0.0f) + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(UKismetTextLibrary::Conv_StringToText(FString::Printf(TEXT("New Version: ")))) + ]; + + HorizontalBox->AddSlot() + .Padding(0.0f, 0.0f, 4.0f, 0.0f) + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(SHyperlink) + .Text(UKismetTextLibrary::Conv_StringToText(FString::Printf(TEXT("%.1f"),RemoteVersion))) + .OnNavigate_Lambda([LaunchURL]() + { + FPlatformProcess::LaunchURL(*LaunchURL, NULL, NULL); + }) + ]; + } + } +} + void SVersionUpdaterWidget::Construct(const FArguments& InArgs) { static bool GBrushInited = false; @@ -39,107 +137,213 @@ void SVersionUpdaterWidget::Construct(const FArguments& InArgs) ChildSlot [ - SNew(SBorder) - .Padding(2) - .BorderImage(FVersionUpdaterStyle::GetBrush("Updater.GroupBorder")) + SNew(SVerticalBox) + + SVerticalBox::Slot() + .VAlign(VAlign_Center) + .AutoHeight() [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .VAlign(VAlign_Center) - .Padding(4.0f, 0.0f, 4.0f, 0.0f) + SNew(SBorder) + .Padding(2) + .BorderImage(FVersionUpdaterStyle::GetBrush("Updater.GroupBorder")) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - [ - SNew(SBox) - .WidthOverride(40) - .HeightOverride(40) - [ - SNew(SImage) - .Image(FVersionUpdaterStyle::GetBrush("Updater.QuickLaunch")) - ] - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(10,0,10,0) + .FillWidth(1.0f) .VAlign(VAlign_Center) + .Padding(4.0f, 0.0f, 4.0f, 0.0f) [ - SNew(SVerticalBox) - + SVerticalBox::Slot() - .AutoHeight() - .Padding(2, 4, 2, 4) + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SHyperlink) - .Text_Raw(this,&SVersionUpdaterWidget::GetToolName) - .OnNavigate(this, &SVersionUpdaterWidget::HyLinkClickEventOpenUpdateWebsite) - ] - + SHorizontalBox::Slot() - .FillWidth(1.0) + SNew(SBox) + .WidthOverride(40) + .HeightOverride(40) [ - SNew(SOverlay) + SNew(SImage) + .Image(FVersionUpdaterStyle::GetBrush("Updater.QuickLaunch")) ] ] - + SVerticalBox::Slot() + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(10,0,10,0) + .VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() .AutoHeight() .Padding(2, 4, 2, 4) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() - .Padding(0,0,4,0) [ - SNew(STextBlock) - .Text_Raw(this,&SVersionUpdaterWidget::GetCurrentVersionText) + SNew(SHyperlink) + .Text_Raw(this,&SVersionUpdaterWidget::GetToolName) + .OnNavigate(this, &SVersionUpdaterWidget::HyLinkClickEventOpenUpdateWebsite) ] + SHorizontalBox::Slot() - .AutoWidth() + .FillWidth(1.0) [ - SAssignNew(UpdateInfoWidget,SHorizontalBox) - .Visibility(EVisibility::Collapsed) + SNew(SOverlay) + ] + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2, 4, 2, 4) + [ + SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() + .Padding(0,0,4,0) [ - SNew(SBox) - .WidthOverride(18) - .HeightOverride(18) - [ - SNew(SImage) - .Image(FVersionUpdaterStyle::GetBrush("Updater.SpawnableIconOverlay")) - ] + SNew(STextBlock) + .Text_Raw(this,&SVersionUpdaterWidget::GetCurrentVersionText) ] + SHorizontalBox::Slot() - .Padding(2,0,0,0) .AutoWidth() [ - SNew(SHyperlink) - .Text_Raw(this,&SVersionUpdaterWidget::GetLatstVersionText) - .OnNavigate(this, &SVersionUpdaterWidget::HyLinkClickEventOpenUpdateWebsite) + SAssignNew(UpdateInfoWidget,SHorizontalBox) + .Visibility(EVisibility::Collapsed) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(18) + .HeightOverride(18) + [ + SNew(SImage) + .Image(FVersionUpdaterStyle::GetBrush("Updater.SpawnableIconOverlay")) + ] + ] + + SHorizontalBox::Slot() + .Padding(2,0,0,0) + .AutoWidth() + [ + SNew(SHyperlink) + .Text_Raw(this,&SVersionUpdaterWidget::GetLatstVersionText) + .OnNavigate(this, &SVersionUpdaterWidget::HyLinkClickEventOpenUpdateWebsite) + ] ] ] - ] + ] + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + [ + SNew(SOverlay) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(10,0,10,0) + .VAlign(VAlign_Center) + [ + SNew(SHyperlink) + .Text_Raw(this,&SVersionUpdaterWidget::GetDeveloperDescrible) + .OnNavigate(this, &SVersionUpdaterWidget::HyLinkClickEventOpenDeveloperWebsite) ] ] - + SHorizontalBox::Slot() - .FillWidth(1.0f) + ] + + SVerticalBox::Slot() + .VAlign(VAlign_Center) + .Padding(0,4,0,4) + .AutoHeight() + [ + + SNew(SVerticalBox) + +SVerticalBox::Slot() .VAlign(VAlign_Center) + .AutoHeight() [ - SNew(SOverlay) + SAssignNew(ExpanderButton,SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .HAlign(HAlign_Center) + .ContentPadding(2) + .OnClicked_Lambda([this]()->FReply + { + EVisibility ChildModVisibility = ChildModBorder->GetVisibility(); + if (ChildModVisibility == EVisibility::Visible) + { + ChildModBorder->SetVisibility(EVisibility::Collapsed); + } + if (ChildModVisibility == EVisibility::Collapsed) + { + ChildModBorder->SetVisibility(EVisibility::Visible); + } + + return FReply::Handled(); + }) + .ToolTipText_Lambda([this]()->FText { return UKismetTextLibrary::Conv_StringToText(FString::Printf(TEXT("%s Mods"),*ToolName)); }) + [ + SNew(SImage) + .Image_Lambda([this]()->const FSlateBrush* + { + if( ExpanderButton->IsHovered() ) + { + return FEditorStyle::GetBrush("DetailsView.PulldownArrow.Down.Hovered"); + } + else + { + return FEditorStyle::GetBrush("DetailsView.PulldownArrow.Down"); + } + }) + ] ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(10,0,10,0) + +SVerticalBox::Slot() .VAlign(VAlign_Center) + .AutoHeight() [ - SNew(SHyperlink) - .Text_Raw(this,&SVersionUpdaterWidget::GetDeveloperDescrible) - .OnNavigate(this, &SVersionUpdaterWidget::HyLinkClickEventOpenDeveloperWebsite) + SAssignNew(ChildModBorder,SBorder) + .BorderImage(FVersionUpdaterStyle::GetBrush("Updater.GroupBorder")) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .VAlign(VAlign_Fill) + .Padding(20,0,20,0) + [ + SAssignNew(ChildModBox,SVerticalBox) + ] + +SHorizontalBox::Slot() + .VAlign(VAlign_Center) + .Padding(20,0,20,0) + .AutoWidth() + [ + SAssignNew(PayBox,SVerticalBox) + +SVerticalBox::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .AutoHeight() + .Padding(0.f,2.f,0.f,2.f) + [ + SNew(STextBlock) + .Text_Lambda([]()->FText{ return UKismetTextLibrary::Conv_StringToText(TEXT("Help me make HotPatcher better.")); }) + ] + +SVerticalBox::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .AutoHeight() + [ + SNew(SBox) + .HeightOverride(100.f) + .WidthOverride(100.f) + [ + SNew(SImage) + .Image(FVersionUpdaterStyle::GetBrush("Updater.WechatPay")) + ] + ] + +SVerticalBox::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .AutoHeight() + .Padding(0.f,2.f,0.f,2.f) + [ + SNew(STextBlock) + .Text_Lambda([]()->FText{ return UKismetTextLibrary::Conv_StringToText(TEXT("Wechat Pay")); }) + ] + ] + ] ] ] ]; @@ -156,6 +360,15 @@ void SVersionUpdaterWidget::Construct(const FArguments& InArgs) { OnRemoveVersionFinished(); } + + FString PluginResourceDir = IPluginManager::Get().FindPlugin(ToolName)->GetBaseDir() / TEXT("Resources"); + if(FPaths::DirectoryExists(PluginResourceDir) && + FPaths::FileExists(FPaths::Combine(PluginResourceDir,TEXT("hidepayinfo.txt"))) + ) + { + PayBox->SetVisibility(EVisibility::Hidden); + } + FVersionUpdaterManager::Get().Update(); } @@ -178,6 +391,30 @@ void SVersionUpdaterWidget::SetToolUpdateInfo(const FString& InToolName, const F UpdateWebsite = InUpdateWebsite; } +bool SVersionUpdaterWidget::AddChildMod(const FChildModDesc& ModDesc) +{ + bool bStatus = false; + if(ChildModBox.IsValid()) + { + ChildModBox.Get()->AddSlot() + .AutoHeight() + .VAlign(EVerticalAlignment::VAlign_Center) + .Padding(0.f,2.f,0.f,2.f) + [ + SNew(SChildModWidget) + .ModName(ModDesc.ModName) + .CurrentVersion(ModDesc.CurrentVersion) + .RemoteVersion(ModDesc.RemoteVersion) + .Description(ModDesc.Description) + .URL(ModDesc.URL) + .UpdateURL(ModDesc.UpdateURL) + .bIsBuiltInMod(ModDesc.bIsBuiltInMod) + ]; + bStatus = true; + } + return bStatus; +} + void SVersionUpdaterWidget::OnRemoveVersionFinished() { FRemteVersionDescrible* ToolRemoteVersion = FVersionUpdaterManager::Get().GetRemoteVersionByName(*GetToolName().ToString()); @@ -185,13 +422,44 @@ void SVersionUpdaterWidget::OnRemoveVersionFinished() { int32 RemoteMainVersion = ToolRemoteVersion->Version; int32 RemotePatchVersion = ToolRemoteVersion->PatchVersion; - + SetToolUpdateInfo(GetToolName().ToString(),ToolRemoteVersion->Author,ToolRemoteVersion->Website,ToolRemoteVersion->URL); if(CurrentVersion < RemoteMainVersion || (CurrentVersion == RemoteMainVersion && RemotePatchVersion > PatchVersion)) { UpdateInfoWidget->SetVisibility(EVisibility::Visible); } RemoteVersion = *ToolRemoteVersion; + + auto CreateChildMod = [this](const TMap& ModsDesc,bool bBuiltInMod) + { + for(const auto& ModDesc:ModsDesc) + { + if(FVersionUpdaterManager::Get().ModIsActivteCallback && + FVersionUpdaterManager::Get().ModIsActivteCallback(ModDesc.Value.ModName)) + { + if(ModDesc.Value.bIsBuiltInMod == bBuiltInMod) + { + AddChildMod(ModDesc.Value); + } + } + } + }; + // Built-in Mods + CreateChildMod(RemoteVersion.ModsDesc,true); + // Not Built-in Mods + CreateChildMod(RemoteVersion.ModsDesc,false); + // local 3rd mods + if(FVersionUpdaterManager::Get().RequestLocalRegistedMods) + { + TArray LocalMods = FVersionUpdaterManager::Get().RequestLocalRegistedMods(); + for(const auto& LocalMod:LocalMods) + { + if(!RemoteVersion.ModsDesc.Contains(*LocalMod.ModName)) + { + AddChildMod(LocalMod); + } + } + } } } diff --git a/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/SVersionUpdaterWidget.h b/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/SVersionUpdaterWidget.h index c3ef6d22..6b59ef38 100644 --- a/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/SVersionUpdaterWidget.h +++ b/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/SVersionUpdaterWidget.h @@ -8,6 +8,58 @@ #include "FVersionUpdaterManager.h" +class SChildModWidget : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SChildModWidget): + _ModName(), + _CurrentVersion(), + _RemoteVersion(), + _Description(), + _URL(), + _UpdateURL(), + _bIsBuiltInMod() + {} + SLATE_ATTRIBUTE( FString, ModName ) + SLATE_ATTRIBUTE( float, CurrentVersion ) + SLATE_ATTRIBUTE( float, RemoteVersion ) + SLATE_ATTRIBUTE( FString, Description ) + SLATE_ATTRIBUTE( FString, URL ) + SLATE_ATTRIBUTE( FString, UpdateURL ) + SLATE_ATTRIBUTE( bool, bIsBuiltInMod ) + SLATE_END_ARGS() + + FText GetModDisplay() const + { + return FText::FromString( + FString::Printf(TEXT("%s v%.1f"),*GetModName(),GetCurrentVersion()) + ); + }; + +public: + /** + * Construct the widget + * + * @param InArgs A declaration from which to construct the widget + */ + void Construct(const FArguments& InArgs); + + float GetCurrentVersion()const { return CurrentVersion; } + float GetRemoteVersion()const { return RemoteVersion; } + FString GetModName()const { return ModName; }; + +private: + float CurrentVersion = 0; + FString ModName; + float RemoteVersion = 0; + FString Description; + FString URL; + FString UpdateURL; + bool bIsBuiltInMod = false; + TSharedPtr HorizontalBox; +}; + + class SVersionUpdaterWidget : public SCompoundWidget { @@ -52,7 +104,8 @@ class SVersionUpdaterWidget : public SCompoundWidget virtual void SetToolUpdateInfo(const FString& ToolName,const FString& DeveloperName,const FString& DeveloperWebsite,const FString& UpdateWebsite); int32 GetCurrentVersion()const { return CurrentVersion; } int32 GetPatchVersion()const { return PatchVersion; } - + bool AddChildMod(const FChildModDesc& ModDesc); + private: void OnRemoveVersionFinished(); @@ -64,5 +117,11 @@ class SVersionUpdaterWidget : public SCompoundWidget FString DeveloperName; TSharedPtr UpdateInfoWidget; FRemteVersionDescrible RemoteVersion; + + // child mod + TSharedPtr ExpanderButton; + TSharedPtr ChildModBorder; + TSharedPtr ChildModBox; + TSharedPtr PayBox; }; diff --git a/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/VersionUpdaterStyle.cpp b/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/VersionUpdaterStyle.cpp index 1b1a4d16..50280acd 100644 --- a/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/VersionUpdaterStyle.cpp +++ b/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/VersionUpdaterStyle.cpp @@ -41,6 +41,7 @@ const FVector2D Updater_Icon12x12(12.0f, 12.0f); const FVector2D Updater_Icon16x16(16.0f, 16.0f); const FVector2D Updater_Icon20x20(20.0f, 20.0f); const FVector2D Updater_Icon40x40(40.0f, 40.0f); +const FVector2D Updater_IconWechatPay(100.0f, 100.0f); TSharedRef< FSlateStyleSet > FVersionUpdaterStyle::Create(const FString& Name) { @@ -58,7 +59,13 @@ TSharedRef< FSlateStyleSet > FVersionUpdaterStyle::Create(const FString& Name) Style->Set("Updater.Star", new IMAGE_CORE_BRUSH("Sequencer/Star", Updater_Icon12x12)); Style->Set("Updater.SpawnableIconOverlay", new IMAGE_CORE_BRUSH(TEXT("Sequencer/SpawnableIconOverlay"), FVector2D(13, 13))); - + + FString PluginResourceDir = IPluginManager::Get().FindPlugin("HotPatcher")->GetBaseDir() / TEXT("Resources"); + Style->Set( + "Updater.WechatPay", + new FSlateImageBrush( FPaths::Combine(PluginResourceDir , TEXT("wechatpay.png")), + Updater_IconWechatPay) + ); return Style; } diff --git a/HotPatcher/Source/HotPatcherEditor/Private/ShaderPatch/SShaderPatchWidget.cpp b/HotPatcher/Source/HotPatcherEditor/Private/ShaderPatch/SShaderPatchWidget.cpp deleted file mode 100644 index 7f190aaf..00000000 --- a/HotPatcher/Source/HotPatcherEditor/Private/ShaderPatch/SShaderPatchWidget.cpp +++ /dev/null @@ -1,225 +0,0 @@ -#include "ShaderPatch/SShaderPatchWidget.h" -#include "FlibPatchParserHelper.h" -#include "FlibHotPatcherCoreHelper.h" -#include "FlibHotPatcherEditorHelper.h" -#include "HotPatcherEditor.h" -#include "ShaderPatch/FExportShaderPatchSettings.h" -#include "ShaderPatch/FlibShaderPatchHelper.h" -#include "RHI.h" -#include "Kismet/KismetTextLibrary.h" -#include "ShaderPatch/ShaderPatchProxy.h" - -#define LOCTEXT_NAMESPACE "SHotPatcherShaderPatch" - -void SShaderPatchWidget::Construct(const FArguments& InArgs, - TSharedPtr InContext) -{ - SetContext(InContext); - - ExportShaderPatchSettings = MakeShareable(new FExportShaderPatchSettings); - CreateExportFilterListView(); - - ChildSlot - [ - SNew(SVerticalBox) - + SVerticalBox::Slot() - .AutoHeight() - .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .VAlign(VAlign_Center) - [ - SettingsView->GetWidget()->AsShared() - ] - ] - - + SVerticalBox::Slot() - .AutoHeight() - .Padding(0.0, 8.0, 0.0, 0.0) - + SVerticalBox::Slot() - .AutoHeight() - .HAlign(HAlign_Right) - .Padding(4, 4, 10, 4) - [ - SNew(SButton) - .Text(LOCTEXT("GenerateShaderPatch", "Export ShaderPatch")) - .OnClicked(this,&SShaderPatchWidget::DoExportShaderPatch) - .IsEnabled(this,&SShaderPatchWidget::CanExportShaderPatch) - .ToolTipText(this,&SShaderPatchWidget::GetGenerateTooltipText) - ] - ]; -} - -void SShaderPatchWidget::ImportConfig() -{ - UE_LOG(LogHotPatcher, Log, TEXT("Import Shader Patch Config")); - TArray Files = this->OpenFileDialog(); - if (!Files.Num()) return; - - FString LoadFile = Files[0]; - - FString JsonContent; - if (UFlibAssetManageHelper::LoadFileToString(LoadFile, JsonContent)) - { - // UFlibHotPatcherCoreHelper::DeserializeReleaseConfig(ExportReleaseSettings, JsonContent); - THotPatcherTemplateHelper::TDeserializeJsonStringAsStruct(JsonContent,*ExportShaderPatchSettings); - SettingsView->GetDetailsView()->ForceRefresh(); - } -} - -void SShaderPatchWidget::ExportConfig() const -{ - UE_LOG(LogHotPatcher, Log, TEXT("Export Shader Patch Config")); - TArray Files = this->SaveFileDialog(); - - if (!Files.Num()) return; - - FString SaveToFile = Files[0].EndsWith(TEXT(".json")) ? Files[0] : Files[0].Append(TEXT(".json")); - - if (ExportShaderPatchSettings) - { - FString SerializedJsonStr; - THotPatcherTemplateHelper::TSerializeStructAsJsonString(*ExportShaderPatchSettings,SerializedJsonStr); - if (FFileHelper::SaveStringToFile(SerializedJsonStr, *SaveToFile)) - { - FText Msg = LOCTEXT("SavedShaderPatchConfigMas", "Successd to Export the Shader Patch Config."); - UFlibHotPatcherEditorHelper::CreateSaveFileNotify(Msg, SaveToFile); - } - } -} - -void SShaderPatchWidget::ResetConfig() -{ - UE_LOG(LogHotPatcher, Log, TEXT("Reset Shader Patch Config")); - FString DefaultSettingJson; - THotPatcherTemplateHelper::TSerializeStructAsJsonString(*FExportShaderPatchSettings::Get(),DefaultSettingJson); - THotPatcherTemplateHelper::TDeserializeJsonStringAsStruct(DefaultSettingJson,*ExportShaderPatchSettings); - SettingsView->GetDetailsView()->ForceRefresh(); -} - -void SShaderPatchWidget::DoGenerate() -{ - UShaderPatchProxy* ShaderPatchProxy = NewObject(); - ShaderPatchProxy->AddToRoot(); - ShaderPatchProxy->Init(ExportShaderPatchSettings.Get()); - if(!ShaderPatchProxy->GetSettingObject()->bStandaloneMode) - { - ShaderPatchProxy->DoExport(); - } - else - { - FString CurrentConfig; - THotPatcherTemplateHelper::TSerializeStructAsJsonString(*GetConfigSettings(),CurrentConfig); - FString SaveConfigTo = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectSavedDir(),TEXT("HotPatcher"),TEXT("ShaderPatchConfig.json"))); - FFileHelper::SaveStringToFile(CurrentConfig,*SaveConfigTo); - FString MissionCommand = FString::Printf(TEXT("\"%s\" -run=HotShaderPatch -config=\"%s\" %s"),*UFlibPatchParserHelper::GetProjectFilePath(),*SaveConfigTo,*GetConfigSettings()->GetCombinedAdditionalCommandletArgs()); - UE_LOG(LogHotPatcher,Log,TEXT("HotPatcher %s Mission: %s %s"),*GetMissionName(),*UFlibHotPatcherCoreHelper::GetUECmdBinary(),*MissionCommand); - FHotPatcherEditorModule::Get().RunProcMission(UFlibHotPatcherCoreHelper::GetUECmdBinary(),MissionCommand,GetMissionName()); - } -} - -FText SShaderPatchWidget::GetGenerateTooltipText() const -{ - FString FinalString; - struct FStatus - { - FStatus(bool InMatch,const FString& InDisplay):bMatch(InMatch) - { - Display = FString::Printf(TEXT("%s:%s"),*InDisplay,InMatch?TEXT("true"):TEXT("false")); - } - FString GetDisplay()const{return Display;} - bool bMatch; - FString Display; - }; - TArray AllStatus; - AllStatus.Emplace(HasValidConfig(),TEXT("HasValidShaderPatchConfig")); - bool bHasSavePath = ExportShaderPatchSettings->GetSaveAbsPath().IsEmpty()?false:FPaths::DirectoryExists(ExportShaderPatchSettings->GetSaveAbsPath()); - AllStatus.Emplace(bHasSavePath,TEXT("HasSavePath")); - - for(const auto& Status:AllStatus) - { - FinalString+=FString::Printf(TEXT("%s\n"),*Status.GetDisplay()); - } - return UKismetTextLibrary::Conv_StringToText(FinalString); -} - -void SShaderPatchWidget::CreateExportFilterListView() -{ - // Create a property view - FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked("PropertyEditor"); - - FDetailsViewArgs DetailsViewArgs; - { - DetailsViewArgs.bAllowSearch = true; - DetailsViewArgs.bHideSelectionTip = true; - DetailsViewArgs.bLockable = false; - DetailsViewArgs.bSearchInitialKeyFocus = true; - DetailsViewArgs.bUpdatesFromSelection = false; - DetailsViewArgs.NotifyHook = nullptr; - DetailsViewArgs.bShowOptions = true; - DetailsViewArgs.bShowModifiedPropertiesOption = false; - DetailsViewArgs.bShowScrollBar = false; - DetailsViewArgs.bShowOptions = true; - DetailsViewArgs.bUpdatesFromSelection= true; - } - - FStructureDetailsViewArgs StructureViewArgs; - { - StructureViewArgs.bShowObjects = true; - StructureViewArgs.bShowAssets = true; - StructureViewArgs.bShowClasses = true; - StructureViewArgs.bShowInterfaces = true; - } - - SettingsView = EditModule.CreateStructureDetailView(DetailsViewArgs, StructureViewArgs, nullptr); - FStructOnScope* Struct = new FStructOnScope(FExportShaderPatchSettings::StaticStruct(), (uint8*)ExportShaderPatchSettings.Get()); - // SettingsView->GetOnFinishedChangingPropertiesDelegate().AddRaw(ExportReleaseSettings.Get(),&FExportShaderPatchSettings::OnFinishedChangingProperties); - // SettingsView->GetDetailsView()->RegisterInstancedCustomPropertyLayout(FExportShaderPatchSettings::StaticStruct(),FOnGetDetailCustomizationInstance::CreateStatic(&FReleaseSettingsDetails::MakeInstance)); - SettingsView->SetStructureData(MakeShareable(Struct)); -} - -bool SShaderPatchWidget::CanExportShaderPatch() const -{ - bool bHasSavePath = ExportShaderPatchSettings->GetSaveAbsPath().IsEmpty()?false:FPaths::DirectoryExists(ExportShaderPatchSettings->GetSaveAbsPath()); - return HasValidConfig() && bHasSavePath; -} - -bool SShaderPatchWidget::HasValidConfig() const -{ - auto HasValidDir = [](const TArray& Dirs)->bool - { - bool bresult = false; - if(!!Dirs.Num()) - { - for(const auto& Dir:Dirs) - { - if(FPaths::DirectoryExists(FPaths::ConvertRelativePathToFull(Dir.Path))) - { - bresult = true; - break; - } - } - } - return bresult; - }; - bool HasValidPatchConfig = false; - for(const auto& Platform:ExportShaderPatchSettings->ShaderPatchConfigs) - { - if(HasValidDir(Platform.OldMetadataDir) && - FPaths::DirectoryExists(Platform.NewMetadataDir.Path)) - { - HasValidPatchConfig = true; - break; - } - } - return HasValidPatchConfig; -} - -FReply SShaderPatchWidget::DoExportShaderPatch() -{ - DoGenerate(); - return FReply::Handled(); -} - -#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherActionManager.h b/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherActionManager.h index eafbbdf8..7d71d95d 100644 --- a/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherActionManager.h +++ b/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherActionManager.h @@ -10,15 +10,17 @@ using FRequestWidgetPtr = TFunction(TShar struct HOTPATCHEREDITOR_API FHotPatcherActionDesc { - FHotPatcherActionDesc(FString InCategory,FString InActionName,FString InToolTip,FRequestWidgetPtr InRequestWidgetPtr,int32 InPriority = 0): - Category(InCategory),ActionName(InActionName),ToolTip(InToolTip),RequestWidgetPtr(InRequestWidgetPtr),Priority(InPriority){} + FHotPatcherActionDesc()=default; + FHotPatcherActionDesc(FString InCategory,FString InModName,FString InActionName,FString InToolTip,FRequestWidgetPtr InRequestWidgetPtr,int32 InPriority = 0): + Category(InCategory),ModName(InModName),ActionName(InActionName),ToolTip(InToolTip),RequestWidgetPtr(InRequestWidgetPtr),Priority(InPriority) + {} FString Category; + FString ModName; FString ActionName; FString ToolTip; FRequestWidgetPtr RequestWidgetPtr; int32 Priority = 0; - }; struct HOTPATCHEREDITOR_API FHotPatcherAction @@ -26,6 +28,7 @@ struct HOTPATCHEREDITOR_API FHotPatcherAction FHotPatcherAction()=default; FHotPatcherAction( FName InCategory, + FName InModName, const TAttribute& InActionName, const TAttribute& InActionToolTip, const FSlateIcon& InIcon, @@ -33,10 +36,10 @@ struct HOTPATCHEREDITOR_API FHotPatcherAction FRequestWidgetPtr InRequestWidget, int32 InPriority ) - :Category(InCategory),ActionName(InActionName),ActionToolTip(InActionToolTip),Icon(InIcon),ActionCallback(InCallback),RequestWidget(InRequestWidget),Priority(InPriority){} - + :Category(InCategory),ModName(InModName),ActionName(InActionName),ActionToolTip(InActionToolTip),Icon(InIcon),ActionCallback(InCallback),RequestWidget(InRequestWidget),Priority(InPriority){} FName Category; + FName ModName; TAttribute ActionName; TAttribute ActionToolTip; FSlateIcon Icon; @@ -45,6 +48,17 @@ struct HOTPATCHEREDITOR_API FHotPatcherAction int32 Priority = 0; }; +struct HOTPATCHEREDITOR_API FHotPatcherModDesc +{ + FString ModName; + bool bIsBuiltInMod = false; + float CurrentVersion = 0.f; + FString Description; + FString URL; + FString UpdateURL; + TArray ModActions; +}; + struct FHotPatcherCategory { FHotPatcherCategory()=default; @@ -71,7 +85,8 @@ struct HOTPATCHEREDITOR_API FHotPatcherActionManager void RegisteHotPatcherAction(const FString& Category,const FString& ActionName,const FHotPatcherAction& Action); void RegisteHotPatcherAction(const FHotPatcherActionDesc& NewAction); - + void RegisteHotPatcherMod(const FHotPatcherModDesc& ModDesc); + void UnRegisteHotPatcherMod(const FHotPatcherModDesc& ModDesc); void UnRegisteHotPatcherAction(const FString& Category, const FString& ActionName); FHotPatcherActionDelegate OnHotPatcherActionRegisted; @@ -84,13 +99,22 @@ struct HOTPATCHEREDITOR_API FHotPatcherActionManager FHotPatcherActionsType& GetHotPatcherActions() { return HotPatcherActions; } FHotPatcherAction* GetTopActionByCategory(const FString CategoryName); + + bool IsActiviteMod(const FString& ModName); + + bool IsSupportEditorAction(FString ActionName); bool IsActiveAction(FString ActionName); + bool GetActionDescByName(const FString& ActionName,FHotPatcherActionDesc& Desc); + bool GetModDescByName(const FString& ModName,FHotPatcherModDesc& ModDesc); private: virtual ~FHotPatcherActionManager(){} protected: void SetupDefaultActions(); FHotPatcherActionsType HotPatcherActions; FHotPatcherCategoryType HotPatcherCategorys; - + TMap ActionsDesc; + TMap ModsDesc; }; + + diff --git a/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherModBaseModule.h b/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherModBaseModule.h index a6c620b4..a43353e6 100644 --- a/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherModBaseModule.h +++ b/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherModBaseModule.h @@ -11,7 +11,7 @@ class HOTPATCHEREDITOR_API FHotPatcherModBaseModule : public IModuleInterface /** IModuleInterface implementation */ virtual void StartupModule() override; virtual void ShutdownModule() override; - virtual TArray GetModActions()const { return TArray{}; }; -protected: - TArray RegistedActions; + virtual FHotPatcherModDesc GetModDesc()const { return ModDesc; }; +private: + FHotPatcherModDesc ModDesc; }; diff --git a/HotPatcher/Source/HotPatcherEditor/Public/ShaderPatch/SShaderPatchWidget.h b/HotPatcher/Source/HotPatcherEditor/Public/ShaderPatch/SShaderPatchWidget.h deleted file mode 100644 index ae8bdf6e..00000000 --- a/HotPatcher/Source/HotPatcherEditor/Public/ShaderPatch/SShaderPatchWidget.h +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "Model/FPatchersModeContext.h" -#include "SHotPatcherWidgetBase.h" -#include "ShaderPatch/FExportShaderPatchSettings.h" - -// engine header -#include "Interfaces/ITargetPlatformManagerModule.h" -#include "Interfaces/ITargetPlatform.h" -#include "Templates/SharedPointer.h" -#include "IDetailsView.h" -#include "PropertyEditorModule.h" -#include "IStructureDetailsView.h" - -/** - * Implements the cooked platforms panel. - */ -class SShaderPatchWidget - : public SHotPatcherWidgetBase -{ -public: - - SLATE_BEGIN_ARGS(SShaderPatchWidget) { } - SLATE_END_ARGS() - -public: - - /** - * Constructs the widget. - * - * @param InArgs The Slate argument list. - */ - void Construct( const FArguments& InArgs,TSharedPtr InContext); - -public: - virtual void ImportConfig(); - virtual void ImportProjectConfig(){}; - virtual void ExportConfig()const; - virtual void ResetConfig(); - virtual void DoGenerate(); - virtual FExportShaderPatchSettings* GetConfigSettings() override{return ExportShaderPatchSettings.Get();}; - virtual FString GetMissionName() override{return TEXT("Shader Patch");} - virtual FText GetGenerateTooltipText() const override; -protected: - void CreateExportFilterListView(); - bool CanExportShaderPatch()const; - bool HasValidConfig()const; - FReply DoExportShaderPatch(); - -private: - - // TSharedPtr mCreatePatchModel; - - /** Settings view ui element ptr */ - TSharedPtr SettingsView; - TSharedPtr ExportShaderPatchSettings; -}; - diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/FExportPatchSettings.h b/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/FExportPatchSettings.h index 7faded44..9a9e052e 100644 --- a/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/FExportPatchSettings.h +++ b/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/FExportPatchSettings.h @@ -222,7 +222,7 @@ struct HOTPATCHERRUNTIME_API FExportPatchSettings:public FHotPatcherSettingBase TArray ReplacePakListTexts; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options") TArray PakTargetPlatforms; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options|Regular") bool bCustomPakNameRegular = false; // Can use value: {VERSION} {BASEVERSION} {CHUNKNAME} {PLATFORM} UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options|Regular",meta=(EditCondition = "bCustomPakNameRegular")) @@ -230,7 +230,7 @@ struct HOTPATCHERRUNTIME_API FExportPatchSettings:public FHotPatcherSettingBase UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options|Regular") bool bCustomPakSaveDirRegular = false; // Can use value: {VERSION} {BASEVERSION} {CHUNKNAME} {PLATFORM} - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options",meta=(EditCondition = "bCustomPakSaveDirRegular")) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options|Regular",meta=(EditCondition = "bCustomPakSaveDirRegular")) FString PakSaveDirRegular = TEXT("{CHUNKNAME}/{PLATFORM}"); UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SaveTo") diff --git a/Mods/GameFeaturePacker b/Mods/GameFeaturePacker index 2f6983ea..78661121 160000 --- a/Mods/GameFeaturePacker +++ b/Mods/GameFeaturePacker @@ -1 +1 @@ -Subproject commit 2f6983ea29f8009897f6eb7f5a886f41859e7e3e +Subproject commit 78661121c5f4de5f2a179c2ba55860f55a95e220 diff --git a/Mods/HotChunker b/Mods/HotChunker index 3afb2f95..3ee11d13 160000 --- a/Mods/HotChunker +++ b/Mods/HotChunker @@ -1 +1 @@ -Subproject commit 3afb2f9578a1ccd8f0fcf749a9e4924c33ddaee8 +Subproject commit 3ee11d13303d35f872926f3de144f9298db3c3fe diff --git a/Mods/HotMultiCooker b/Mods/HotMultiCooker index 0c7ac4d2..c36e8cb5 160000 --- a/Mods/HotMultiCooker +++ b/Mods/HotMultiCooker @@ -1 +1 @@ -Subproject commit 0c7ac4d27fe84d24da01eb0dcaeae369a85097d6 +Subproject commit c36e8cb5f4453114768f9bed70b1f9ff132cb8d8 From 5bbc219aa2c8557d26067f58e99b0dc66a283bd4 Mon Sep 17 00:00:00 2001 From: hxhb Date: Wed, 4 Jan 2023 09:23:19 +0000 Subject: [PATCH 18/81] delete GameFeature Mod --- Mods/GameFeaturePacker | 1 - 1 file changed, 1 deletion(-) delete mode 160000 Mods/GameFeaturePacker diff --git a/Mods/GameFeaturePacker b/Mods/GameFeaturePacker deleted file mode 160000 index 78661121..00000000 --- a/Mods/GameFeaturePacker +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 78661121c5f4de5f2a179c2ba55860f55a95e220 From 7d1f589db7c0fa2fa29b999c6499cd0658145128 Mon Sep 17 00:00:00 2001 From: hxhb Date: Wed, 4 Jan 2023 09:23:59 +0000 Subject: [PATCH 19/81] add GameFeaturePacker Mod --- .../GameFeaturePacker.uplugin | 35 +++ Mods/GameFeaturePacker/Resources/Icon128.png | Bin 0 -> 12699 bytes .../GameFeaturePacker.Build.cs | 53 ++++ .../Private/GameFeaturePacker.cpp | 20 ++ .../Public/GameFeaturePacker.h | 15 + .../Classes/HotPluginCommandlet.cpp | 78 +++++ .../Classes/HotPluginCommandlet.h | 17 ++ .../GameFeaturePackerEditor.Build.cs | 69 +++++ .../Private/GameFeature/GameFeatureProxy.cpp | 85 ++++++ .../Private/GameFeaturePackerEditor.cpp | 35 +++ .../Slate/SGameFeaturePackageWidget.cpp | 283 ++++++++++++++++++ .../Private/Slate/SGameFeaturePackageWidget.h | 60 ++++ .../FGameFeaturePackagerSettings.h | 73 +++++ .../Public/GameFeature/GameFeatureProxy.h | 24 ++ .../Public/GameFeaturePackerEditor.h | 18 ++ 15 files changed, 865 insertions(+) create mode 100644 Mods/GameFeaturePacker/GameFeaturePacker.uplugin create mode 100644 Mods/GameFeaturePacker/Resources/Icon128.png create mode 100644 Mods/GameFeaturePacker/Source/GameFeaturePacker/GameFeaturePacker.Build.cs create mode 100644 Mods/GameFeaturePacker/Source/GameFeaturePacker/Private/GameFeaturePacker.cpp create mode 100644 Mods/GameFeaturePacker/Source/GameFeaturePacker/Public/GameFeaturePacker.h create mode 100644 Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Classes/HotPluginCommandlet.cpp create mode 100644 Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Classes/HotPluginCommandlet.h create mode 100644 Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/GameFeaturePackerEditor.Build.cs create mode 100644 Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Private/GameFeature/GameFeatureProxy.cpp create mode 100644 Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Private/GameFeaturePackerEditor.cpp create mode 100644 Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Private/Slate/SGameFeaturePackageWidget.cpp create mode 100644 Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Private/Slate/SGameFeaturePackageWidget.h create mode 100644 Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Public/GameFeature/FGameFeaturePackagerSettings.h create mode 100644 Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Public/GameFeature/GameFeatureProxy.h create mode 100644 Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Public/GameFeaturePackerEditor.h diff --git a/Mods/GameFeaturePacker/GameFeaturePacker.uplugin b/Mods/GameFeaturePacker/GameFeaturePacker.uplugin new file mode 100644 index 00000000..109a130b --- /dev/null +++ b/Mods/GameFeaturePacker/GameFeaturePacker.uplugin @@ -0,0 +1,35 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "GameFeaturePacker", + "Description": "", + "Category": "Other", + "CreatedBy": "lipengzha", + "CreatedByURL": "https://imzlp.com/", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "CanContainContent": true, + "IsBetaVersion": false, + "IsExperimentalVersion": false, + "Installed": false, + "Plugins": [ + { + "Name": "HotPatcher", + "Enabled": true + } + ], + "Modules": [ + { + "Name": "GameFeaturePacker", + "Type": "Runtime", + "LoadingPhase": "Default" + }, + { + "Name": "GameFeaturePackerEditor", + "Type": "Editor", + "LoadingPhase": "Default" + } + ] +} \ No newline at end of file diff --git a/Mods/GameFeaturePacker/Resources/Icon128.png b/Mods/GameFeaturePacker/Resources/Icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..1231d4aad4d0d462fb7b178eb5b30aa61a10df0b GIT binary patch literal 12699 zcmbta^;gv0*Zs`U4U&S$&|T8qCEeZ9Eg&T@fV6ZsC`gAiNDN4~NP~2D_b~7C{TtqO z&%XQTd(K(+?0wgb)=*Qx!6e57002ixQC90ehW-!esQ>N1#VtqwBMf&%Lr(y}BK#jf zKz1$}0AQ*+$jE4D*t>bTdD^?VLzHA>AnqUCY#p3!0Kj)CPuosM`+!93ZuMGPISQJp z?50JG4$+d1g%Tw(uux;*zmK9WS|rx&A&`?prWh)WLW+-vekImq!;ZmRK-;GN79aLK zDrV$qBjCH!T*uw+_)F8g_+HgjUc)3B3>`aNkw=pcid`=KmS8<>uy0^vn?o`Llg=H$ zM{oE*?Fpv^0rx?oqO3G9v@QVT`xgrxfT`xdxZXq}@D8Q3OhC{tAedK@pfWm?2$1xT zm;M1r%7dVJnGD)MAu?bwYHhUzXs`nojKRBq0chTRRsaYvPNgOW6(#`?LYpXAz+MEX zn$(Mt0}QwTB3tD?Az*^LOfIcrRh_$YBOLV+R}XG z5igtl_3B*-O|*0}b3gqw;=|?|+Y^%b8Xr*SC=LopVlOkbM!HpI#5eGQZQcREIlI=mKs7Qw4`2&0$Ifv(8i;aW`*BV_b4L2ilu`LM-ge#C@1kLa%;utKy(!; zFU3BBg(6Ml+ml3wfOnzK5giKLsUh{6Vl&uHGHqo74Xr4$WR4Ad4B%OG#)cnOv;1Tc`kX!bJFq?9Q)GPDys^pRP;m~XgrKWNx7u@TiRc8ds6#5huVFwc7lItZ`CrU^ruG;6!tUr zk*J#RIFBD>0arM>Liq#X$RKG>+)!Cm1E4LSL#;eX&h-&Xxo*Gltot9 zmAUCi6bBi?qfrfitNd1%Db_6fX};Al0Ku|;-Qdec?SxYq;T^))$MAD}@$)B^Uzu>q zU$J5p%cZ6(mQGCl5dz0@%Fm`XFQf?`&Q&X_luDSq&(v~k;*I8~%) zq#IN!R%%u%9Ch;7oRsGM=#=|q_!NRGHTa&|JO$|qd zQwc@UFIk^%*V5C>{4O(SzKUDvs$b{cSVVwm+iZXXWGM@xD3?m~7E)xeT}rd}lyqpk`23Jybo- z)>3Wz!Tdu+MMPzAd~E#N_*@oWju`j+yS<#focWx!77HU^Bev$U=2jb}`fZ~hhNsOP zuHi;Ph9w5NMy3t&)p^zQbHA#8l@gS;simk@=Fi#vuDfU+ZZ21 zJEZ6ksSsoE)4l&^>h5?6;boiK`o$BeuZ3+=#8L^N)uB5*)ztPw$BEU{cYB!=NfQpZ z;Tl2vb5m%RyOy!PgRmLHBg6G0B;wtp49Nd*XYl#_S&{KvlYNv;mtD=V<5m}{Wq;4d zB3{AaD7qxj&f6|Az+r1RHfxY)pyaIlMu>x@hTqk>Ywh{uDsnS#6KgAgG?R14)ZMRW zqW3zyl%$;F6`OFnq)L>UVCuOPK1&(NSNcmrANqJqzh25-I~vYE{C}brWK3Azs$D9w zsQM=#Cw1`o(e?9`u+lRGRqDbYi^f?74D+3wJ8 z*Y?wBl}&j4OTTMu3+LN3v|*=)#3~d+cFbn!ANx8+O!F*g^>#M;w%y~=BSPtw`K;q7 zV+|wAi2}K21&EVZy{|Tsn@b{;_1P&6b~~#ah3Z8;{FX7dh*4N0^iZorTVtA8TxQiP zPxLctf;t)eRh>f2dPYKfnm|rRSh|=y;ekgh^Czb22Aqa#O_q-lc@*Nr(J?hd%cL2^ z!3#_)zB?3=ZX?}UE2)j;m3?g=CT*u}4|Z4C^Nn%SD>8O7a9wd0ml|=_^cqiYZsnFa zGsc;ge}y&6w0-XuZSAlr9iA8$k5q;Xj@J*JL?=@A~JIBB0}z_jq>MxZ@5k zKHRme3({4cwVkzjQhI8*lcFmpF z`5f)+Cu1w)cJ(pwKXZqx{?7`_RCu|(qK1C&uXKhTmJUMyrr2Fhe$7kE3k>3TSg~0C z)*P^BJ+bD9=XTbP@3k>4hlt%1=@6MPxoq{itY6+C)Nj?#t`#rTH562#nWzL40z&MSYnyZ*bIHIjcp9~t2jqrVn? z7*DG^)H}?tB~PRlW&TCZN*KSaES#+bJHmVlul}qk+@XetO}-@EB;d)QBxEIwM&Lvo z9&WR1y{D5NpA{df4_o!AuDIho3jvQ>9NSuTxSG$Vi!2&(=Kb z%m3+3h_#}YDggM?|EEL40N?@fA0GgKHx~dLS^$7>CIFDSC7bul0|3K-lB|@D@6vIg zUn1SS;ojNP>S$%fVW z#12W5G<6LP^A;bT0=v(A6_TS0O_j}`0llI>mpYs z_ua-5ci#0whKVQN93R15{6_uVehg4Euk`|D@RU&F{SH*#&b_LN&|;^jR96dZgv#CS zjYCRIa7~W#;;dUp88xc;#T&(d{&lIY9_ZlJxmt|7CR0e4B&^g^68QiSZd#nLHcs>g zS7F~b_R1Py-n&YkeK=^W0qjs;vv1&R%x^N~VhZK7c=%=jX0s9uVM^HrGpp7sx>pcCh@s?Z6#4M;F&Bb4;%rgn!{ zf8A<+pdy3t&4>~BPMQVT8(Bh?!P|%;7E&X5tp9B9S>+`~LOBWI1G-5TE-nD%z|%!fM@p4h zpy&YTiA5jH0fN--j+JLJl&y=>8M^-WBh06Hph_Bmq)hnJ9Jo$W1xY?3<(Td$9y&h@ zLyI>A7Uj)q!1d=o(O$7fGz3a0+e%2USHKaaL{jNM4IxH52p-CTpBMXn{hM`FxrUYq zfiMLrWWupqg8RT3`CNDDXsz!!0J6$t)iGv8(KC;Y9;IUoFD9)7%8!NnY>x{yAOj$1 zl*enoLs=*k$yF<~WO~?@Ex5eZYMd3e_+A1?#9QM&lZ z{nZrIA0_&Pp|6}qo~oG7bYColkn+j;a@zn~8eIv>StN0SNNisxsR^lt9(w$rEY)!& z&Z2=BiV=V?HAm1mUc_EHB;c13EL$Dz1{3s8RYMU_JV>^$-BUCXc}Y~P2(>>_T{=4| zr;;x=Jj&PFZK-Z@$U?TLtCh@0Wk%788QS`a9s^>)&l4_)!jBF!z?x>WdPh@dkfFwE z$D-dbEunIJQvc&JN@-8czeiE74>lv876np#%}Mq?GjP7h>OOr4Y+r)j%aT~v*f78% zs*@*io-x)#JiK~cbg#h@O3Wtj=;wDnJ(9L%q<#@qC;YBR4Uj3M@tAq6h=Nl zj}Kc^k;MMGCvNrIJ`feA2V!Qnu`=(v<({>QRQ)LXxjaqSTb_bM9jQ?}xP3P$4y zdJ&Hguo<4CMguj7`iXA`vv~Dx^NV6Qogq8Kia6rEf<76~-AggQzeYgdoxSM_yH&g) z1tN>@Dsma$cw%#P$cPTQeyniL_StUQkWxS1iqoCuWJx=2rD82ph;1o+f4Q=!6NzR4X;_uw4gVIY4sNl;4oxe8ivoKg;xvUI}qz9 zBn-}O1y^?Fw?vkh{z{7h@49C!w4!g)WjvYOHWe6mDI7aN-{}KP&?JePXlHSDcsuVmZ)WsJIzS%0ly19Px0i8coNv2edS{PU& zD#d8ZR81uNj+uWp{SnNnW@!2&aTmIwpI05o8OInrji(Tih8cjufvgxpM3|ZZsufM# zBXGbg7L~Nw25dZ_5L&aGwoM5IZXDGKUBo-8i7I@JpD{Nu_;+bP z1LeMlFIEBMPZnXbBsSEj_ddcv$5&_Ta)KB^6&mp|!ai=~%E{RiA zRzaI#eU{m?&q_93W_ihh)8d7qiMNtfpb;KW(il!6*g0J)YO%MfmUj1KEGWd_37@gF z0){+%i1gF@z%xkj-3CgSL&kKMNvxSCrX;Iu3`#~}r`c~7(OqZJ0T!>3BP8IqH_p>R z^aW?{c(hNmDy-+7q)H#AEO}PY$6$vt*biXBhDJ5go96o1?rJ*i4luEw z+1@@HhNI{O=?sP`vX&^zm9YAhT-Uw1g?OXC&lnad8Jcw?e*lN8tlO4d+sh(Ald-I#3V~!(cg{ct*V$oRngnx zYRZ4PKeT-UzT_DC6-9Y&YAMSWcXS1rk5M{^UL;2|zO~Y0Oyww{{A#J1Kt5gR44=^? zHUTF_`s;HhfeA$13maC<&?UvjN2M6jg7pmXhgg>N@wfqW3`vqc6_)xKow0U17W#ap z>BWDLE)v2E;UaY5ykrWj2q8brVmpV(9+YE-6}&vm)b0b!2Q( z*2G$j_@XI6^e^fzemCl0O84NV0|z}JTF<#wPFGt(BD@mmnUMIbP7uRMG+9a?VPsYH zi(9=efpI5B@q4JK>iWB%MmTkII@l0{lX7*#0{Axyy5`;2JT0I^@iHyLCkpIKBTq#ymvf- z`F8j3hi6SeV;Vi19lWpHk*91Szt**Tc)UTO4LJ=8s+fsqgdh3!98T_0J$5s{m zLzi>LZbcPD^WZ<)q4l%^>qp5zXbiO&0ouH910(}11ARu&x~!j=O-!?x z_4u*R#x1xB5 z)LGbvSyDfym8ejr&kP42=_huk4v>h%qU#@di>!t`0m_e|V$5X8ZGtMxO%qw+^ce}J zR7Q@X#oE$F%9@Zc38vsts~1x$I*1mjywg@p!T893n;E9M#Oh*0{8hv_kS~t$M~8*| zI5w`3Ic8m^WHP2Al9g<^G7e7x#X{BpK@+^eCH00g2LPxS&*S2pJM-X|gxovU8z5YF8BTe=8|`)T%oTK?=Ax?>g1)*>0XI zh!MNc?f6a1S&^zU^0OmcXatpx+aOD9q_NMBXH zcteYxjadqLLaA*;z=0F%ITwkjWYRvnKSp`_v`zC4|8s8xj);mhFU&%L5p$g z6Gb>2Ck7x^HmYf%_7*9)k55sJdxB*~+HJ#F{Lh7+P0WPqx#-`?N3&Fy zv(XLt+zFVG)fCsEGrbrgfv}J-$dQbX@>(*#-aSkPZB&j}yL)8IJ#W?%NLlrjw2>QR z41!7O)ZUSHkO&M~>ynR`* zC9ixLKm}f!l8y{gra>shS9fuALo`A7dt30lG2M=3CGFEEP-tLRnZjT{`%KEwx*ffw z$0^Z0KU&@)-B3-OB80ui+jl%7qhA){r8W9;KqAU7Q z?VZ3n$;9mHU4cCKsu!D)cv;c8$s!r)k!JsxYs> zjXq?W?icPuYfbp1)gMK0R2nHR&ME_>X0#i=9`X@cogiA`WdOs*GFhiRg-WCukahJZ`Gbvp(q+~_daG~-4x$Vh$qC1YrDguY}qe@6a_T#V=F8@ zaY>$D&|8LQ^vC;Gz8)24=-#MZ&~=YXzL4>m%^BwHM)Y6;jIX1JAWsrV)5wNd)JnD2 zh8ls-SoX-?^oPqd$dWS!f@J)>hn~zys&QRPHT?P6VNWm)dGl5MkK<_NFS?oanE#1%b;-?SB3mE!p#F zN}IYu&H@e6nqFdGirCy(XPhKORot46u<(Dj=kL;y>a?#k<7|pZ)BKetCs~(txpe9P zVTkf550T3!C*tii8ra7}Q1xcmCxM!aE30+VNk)sPpG`Xdh$~bcQIPvjDY`03l!@FA zyWUO=jFjxOBwZqyQ@Tjj2`6-@YD(6g_&wZLvL0xd5i(|iA4{jhLp>cfO+LOkPD?xW zFf~GCUm#eCk-Wga{%ww)xPCPTIvfxgZ`XpFJR6(dK1Tx~H9<{M^oOV5hdsHTk|-O3 z<=Qr{&f6zWf+S^C;lL&(TUTOI37l_cJ2ztM4}pO|5>Hyi!o3`rA&sMz17xm^rFhr? z1PJ|vWnG5|umY3?EFBao56^gD$)ox(G5Wu5iZ3`_G zk=etx_Ld{J%f#-kFSURUKR9(6cOtuLjYFYc#{d}*vB z+MHiwifwGWzj-n1nhk&Hr>s#<Gs|L5YMDC2lcs z=HAVZ*-Cb+T*KEN9M(@hv7?25#+~?6a~Me?m#OF1hO~~G`}I^l>aqqan1Q2ov-6P{Ax`Rtqy`vLw?J{f7zmykPi9Cn zezwzl812$SV`ZB+y% ziUb`Z$y|1Nw2n|mk|@tV-yHer()W_EZ*k7}?Ec})!quU>z$>XfvJ@3{`q_(lPO*WOXZdlKg=>hcgv&E? zIM7vxXb4ydmxVU4V|#bj4}6Z3$Q_orEP?Kycg~AHina%H6&DW|$5amT;|JUY^qhBJ zeorExDe0q+_GBPd!tunf!vsTz7I~}3CRHZr;laFhC#!b4XVrm|RLgBAalcOw^Nb%q z5&h-zf9|(FtC~69aX9414`aSk?OV+D!dDz_b8c+2lKyGXdfNT@z?2s6<(D~E0(>?s z<4eV~@!{IH@iFZ?mpBy(HqwrROVbSVZvhav5_eQU9${|gbW8AN^I8Y)!qrIl58xm6 ziy-T(V~Ks%z5UL__Gdz((Rtw^gu}d5vO|KdSIKn$ug0}yECTL>>r^G%-KxA`x!e#^ z=hnIZ47A}xS5v&*uBPAN`i>N@&v?xr!SR$Wjc~>h@cQ%{$38j)U>yvV5bJw~0?aj(DH01FS4>`1Ud@sWk zO27rtW!x=P`k|0pomO2fwxx2TxmUqS`I^&Ict+ysA|ymQnCwBE+mr84xPsa0%^72X zkS1aN>bFj=^DqtnM^x`}USRSLwm5d{Z1tX>RVZhh0U#`DS!Wj{tJd(p-T8^;)_J`z zpFX~zQAVToCVs+jY;63XTqyQEU(a=JKkMM5W-NRBglo^w5&Da=c0XsnO`sDKQs8jV zN>5P1{g2|yjS>tQNbxycMJ#+gI;(oFXu7KH(Lw|g@3;1ok=_7N;bj8`o%z{U z5;@|<5tPuGwWbT$pS_FY7mPYgE^}3GAqC$+XXGos9xoTb+E(Bzy&xl={&$LC-BQki zFTK}B7+?{U@Dr$;67tdhYDC(Oq)Kq7i+eBI-LsUXG0WyaZnY|RtaecM%`^2?Ww1&K z+-=O9T@7>lSXo41P(R|&GY*(j(V0lDNZw!{tr9TuLk~rlDxw-Q*q>q zeI1rh4W1lAzVC7aH`97^B=bzJ+0b?AX=OsiwITRgc{nXvKm#a@W>Fr&y%;*OO zbgdo-r83usKQ}$}XzkQa)*ZL+3p~A;l@I2Nc5tgX$TH{SO0Ut))OJ5C?a(S%U&@$U zt{lr}afDy`!({8?VehGbf=}M$j_N2eM|{Ff$H=EK_<)sK_LO)s;Xt<+oj% z1(S6*ghH)~3NbGS0`eb^)n5+!=Uz8zeINj?J-ff7%DFp{+;PsRbbXAF+B-n_P92#B z!)+Mdx=#ikd{%?B{p(le?+RYdVF}CI9}r_5Ff37bsgM-sc7S5|uW0BQ!4N^_QK5)| z0vA6c8bK5#FOS#n6%>Gp1WOD1AD>evr-hI}-b5d}%Gi{cRBIisXcT&qTem;z&i-E! zKmTqjiKm}&SIaFfIcv?{-$gHaQ}3qcQ*va}J|*dgE3+t8%O#V$XG{MK)x%~Ar5P?U zmrM=Gsn!W&dpp!%K##oj#w5GESNe{Dz-#KsTK~WML|?D6BY@f#)M(O+zOO(L;EsI# zJh*mu-NT_YTfP?R+IjI23$U`gXbR@)*H0KyCq(Hp!z;Ag=<6*enKP&>U6+;QXmGVg zc~4MgS>OrA0yjv0v~o8isq^DYtUrX@r1idBWL=0`cx(N#dHq``{i!A%z8}Uw)Du7s zmmus~y1r{)ToN!Q(dvxXsSVg|8c}pyxtRk`5p=i%!ux2ubqpcn z=0~h)t)CsG#ccwM5WVee^lT)tL6gU%W8v%Id(qqm+SfluKaxVxlMQhQq*(pzOD4{2 zsXR64_jb+Q6T}|K<8w3HdJS4YbkbEt&q4QpxKhnWLaM@;u(bb}p3YQzKkNxBUBcB! z;xj&XZ$EvP{*%MmwKrH3WI@%LhFLLXW9IvUOFb4{GLa^zK$4oW%YDr=M)ZFe@1SLEkh8^{&#A%dqkOqY-fex;iZXa z0nqWc65+XAhD-XvE8&E#kBPby(!`&@$~XP44Qt#y5fP{yXS+rcaASe4>h8e?slwl@ z-|kN5)zV*{=eurr81-UANu|kKnKVAHO-}xM^Cg@z7NC7Re4oD%C)T*Xt6Q1IPEWv^ zDi-kLv_YzEWv}xyM*!H;j3_yLRbnLIK*^>DLI8`uY#QN_o|$K;MN5)F3JjYM-cNY8 z>pCaI0G?lheHE@R&H_Z(KKG65RZW8y-Am$P15^a8&1b?dTWnA<{KQ7~c2y>v5m^&us34Y|V@ zlqhIsp`f`JEbox|0|`)Z{b+!&&Tz}`qKooBKBXjzG9XK_>T>k38vB+ms4`9`D2ys- z+`r*LRhvsz&pGi=ycyx?w1$#97qree=p(D?WhypXdK_^g_k{c1)e%p5wM><2@jW1) za#&TKUg}lEtEh$?Q%~OY&3T}W7T{>uZfCV;GsU-w)%~!BUMP5lfVjW#K0SV~%|prM zW163_u}&c#Q&B(Cua0~_ZspJ4e>6y>V$?r;fL|NuCYOso@(KO#A(ig1O5n8opA60j zE%(Y#=B6)4i^2qfILZ=r!ninMS9EE=AQ5`%{HG6)~7-;Y@W~m);U^4jBgV* zb&27D7vzTbLrA-?w-QXp93bRQ&wdoh=SZsNh<<4n-^UBPf8=3har!~-j<@$di23L1 zq=dM)7hLu5M^TEQd>J`E^2};oxh#rx75aKDH$BvvT9Is&K)-?znkYrHDH$LwL5@y24vK9_bRCZDHjQmHSo1COORCw6;Nc^>L$B&g=aKa z*P=OiqyAoAi`Sae;Gbbt-(uo?=(U+&uggSUY}(neK>a+PnZx?~inkAAKt2H)Wf9kZ zzd!(O?6__+7e3cxMQ+jxeaeOf=11XH^A0JO_srr!vcxXNs-+zM`c&=^dTsC2TDxEA zl99DxEvAq}V3eo?&TG9r+42yFs;kmQ$g3vq)OagA8NzI}T8RjEfdGgmO(4vpNy zT|dRvqUBD=T5iz50G=F@gX7HP_a>8}44iI)Yost5RB`3np-VL@Gt9;h@C z6GA5$FY4aAkmMz{{{pZ$+&)78X4Z;CvUKN>OT23*zwv-lti-RKXHcYyDJ_^o z6ZO~=1VRoay_R|qBLw_)7bvL2H0g~tLreO@^T!cBJt!fv*D|U>aAfEi@6*$4-7~+y zD(HU3<_>;PMT+yH=W@DGvvj=S-04X1T`z0GD&k%zJu5_gDhRZxRaS^+Hgg6PkFcs8 z*$+vnsQQVi6IQBI1)pj^@teE^;Ym}3=DScs9e;Jj@z48e5{I5T#awr1md>$K6$O!0I8 z{Rk%+=bKF4rYs5675%;e!XLt?(beOfFE>;=YwiX}BQQjKWCQV`2vuU0i{j_^+ zj?S^(#h_6Mygf)o6o3fY{pue!b%#m12af^}56VFfqenmZcXG?~e~wJA&(u^Waw`0A?6P-3` zmGW0Hkq}80#uvKUY8CBr@$X|qdtQ^VU@h{(PwT;WE^If~`g6|alt){+{baJ4&9oe- zK2B|Q^Ivpoe#^#S`H!@MaqCMF`pf5SC&~Qm=rac!B%?GT;%k>{*NeL#NP9K#2_hwO z-iESn_Pf$`!6>O{QBH$G;-CFRTw%_S`2qNJ1li1aS006dZ0K&lUlw-JHIBlzyE74h z!8l|^iJ%=K`F%wITBUr4^6Z4}MEUbtM@r7BHWIWQbT51_4lUg1Tst@YF3p=#C=_OY`xFQL zfnz*<-IavyUEj*^P6JD8W^!1yCScorz&X+8fkTRDOj9TmA79aAEH(f5WCM+dqz_!N(z2Yc$k256D`7 zokD-nLN;IloasUxE|xHTmudJK*|lVNJI{>hCrCl3u3*o1lYsE<%jghb^beRP;wlR7 zpAUOiD@Q)$Vj?dBR;1AV$qu*?!df~1wxi}5!qGU6ksnFloq5F%V@?-4$yNwQs0#{^ykl?EYK&=dPQZ8veX{Vob3^yttw8^cc{bu}|E*TaPekZu$QUxtSLP a;7#~yJh_ha>A&A^fRdb=Y>l)<=>Gxy=2LS3 literal 0 HcmV?d00001 diff --git a/Mods/GameFeaturePacker/Source/GameFeaturePacker/GameFeaturePacker.Build.cs b/Mods/GameFeaturePacker/Source/GameFeaturePacker/GameFeaturePacker.Build.cs new file mode 100644 index 00000000..43657a8f --- /dev/null +++ b/Mods/GameFeaturePacker/Source/GameFeaturePacker/GameFeaturePacker.Build.cs @@ -0,0 +1,53 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class GameFeaturePacker : ModuleRules +{ + public GameFeaturePacker(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + // ... add private dependencies that you statically link with here ... + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +} diff --git a/Mods/GameFeaturePacker/Source/GameFeaturePacker/Private/GameFeaturePacker.cpp b/Mods/GameFeaturePacker/Source/GameFeaturePacker/Private/GameFeaturePacker.cpp new file mode 100644 index 00000000..4182c52b --- /dev/null +++ b/Mods/GameFeaturePacker/Source/GameFeaturePacker/Private/GameFeaturePacker.cpp @@ -0,0 +1,20 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "GameFeaturePacker.h" + +#define LOCTEXT_NAMESPACE "FGameFeaturePackerModule" + +void FGameFeaturePackerModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module +} + +void FGameFeaturePackerModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FGameFeaturePackerModule, GameFeaturePacker) \ No newline at end of file diff --git a/Mods/GameFeaturePacker/Source/GameFeaturePacker/Public/GameFeaturePacker.h b/Mods/GameFeaturePacker/Source/GameFeaturePacker/Public/GameFeaturePacker.h new file mode 100644 index 00000000..4d1c1df2 --- /dev/null +++ b/Mods/GameFeaturePacker/Source/GameFeaturePacker/Public/GameFeaturePacker.h @@ -0,0 +1,15 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FGameFeaturePackerModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Classes/HotPluginCommandlet.cpp b/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Classes/HotPluginCommandlet.cpp new file mode 100644 index 00000000..2574804f --- /dev/null +++ b/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Classes/HotPluginCommandlet.cpp @@ -0,0 +1,78 @@ +#include "HotPluginCommandlet.h" +#include "ThreadUtils/FProcWorkerThread.hpp" +#include "GameFeature/FGameFeaturePackagerSettings.h" +#include "FlibPatchParserHelper.h" + +#include "CommandletHelper.h" + +// engine header +#include "CoreMinimal.h" +#include "GameFeature/GameFeatureProxy.h" +#include "Misc/FileHelper.h" +#include "Misc/CommandLine.h" +#include "Kismet/KismetSystemLibrary.h" +#include "Misc/Paths.h" + +DEFINE_LOG_CATEGORY(LogHotPluginCommandlet); + +TSharedPtr PluginProc; + +int32 UHotPluginCommandlet::Main(const FString& Params) +{ + Super::Main(Params); + UE_LOG(LogHotPluginCommandlet, Display, TEXT("UHotPluginCommandlet::Main")); + + FString config_path; + bool bStatus = FParse::Value(*Params, *FString(PATCHER_CONFIG_PARAM_NAME).ToLower(), config_path); + if (!bStatus) + { + UE_LOG(LogHotPluginCommandlet, Error, TEXT("not -config=xxxx.json params.")); + return -1; + } + + if (!FPaths::FileExists(config_path)) + { + UE_LOG(LogHotPluginCommandlet, Error, TEXT("cofnig file %s not exists."), *config_path); + return -1; + } + + FString JsonContent; + bool bExportStatus = false; + if (FFileHelper::LoadFileToString(JsonContent, *config_path)) + { + + if(IsRunningCommandlet()) + { + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + AssetRegistryModule.Get().SearchAllAssets(true); + } + + TSharedPtr PluginPackagerSetting = MakeShareable(new FGameFeaturePackagerSettings); + THotPatcherTemplateHelper::TDeserializeJsonStringAsStruct(JsonContent,*PluginPackagerSetting); + + TMap KeyValues = THotPatcherTemplateHelper::GetCommandLineParamsMap(Params); + THotPatcherTemplateHelper::ReplaceProperty(*PluginPackagerSetting, KeyValues); + TArray AddPlatforms = CommandletHelper::ParserPlatforms(Params,ADD_PATCH_PLATFORMS); + + FString FinalConfig; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(*PluginPackagerSetting,FinalConfig); + UE_LOG(LogHotPluginCommandlet, Display, TEXT("%s"), *FinalConfig); + + + UGameFeatureProxy* GameFeatureProxy = NewObject(); + GameFeatureProxy->AddToRoot(); + GameFeatureProxy->Init(PluginPackagerSetting.Get()); + GameFeatureProxy->OnPaking.AddStatic(&::CommandletHelper::ReceiveMsg); + GameFeatureProxy->OnShowMsg.AddStatic(&::CommandletHelper::ReceiveShowMsg); + bExportStatus = GameFeatureProxy->DoExport(); + + UE_LOG(LogHotPluginCommandlet,Display,TEXT("Generate Game Feature Misstion is %s!"),bExportStatus?TEXT("Successed"):TEXT("Failure")); + } + + if(FParse::Param(FCommandLine::Get(), TEXT("wait"))) + { + system("pause"); + } + + return 0; +} diff --git a/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Classes/HotPluginCommandlet.h b/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Classes/HotPluginCommandlet.h new file mode 100644 index 00000000..60a7dda3 --- /dev/null +++ b/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Classes/HotPluginCommandlet.h @@ -0,0 +1,17 @@ +#pragma once + +#include "HotPatcherCommandletBase.h" +#include "Commandlets/Commandlet.h" +#include "HotPluginCommandlet.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogHotPluginCommandlet, Log, All); + +UCLASS() +class UHotPluginCommandlet :public UHotPatcherCommandletBase +{ + GENERATED_BODY() + +public: + + virtual int32 Main(const FString& Params)override; +}; \ No newline at end of file diff --git a/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/GameFeaturePackerEditor.Build.cs b/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/GameFeaturePackerEditor.Build.cs new file mode 100644 index 00000000..1f0837a2 --- /dev/null +++ b/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/GameFeaturePackerEditor.Build.cs @@ -0,0 +1,69 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class GameFeaturePackerEditor : ModuleRules +{ + public GameFeaturePackerEditor(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "Json", + "JsonUtilities", + "Projects", + "TargetPlatform", + "DesktopPlatform", + "EditorStyle", + "HotPatcherRuntime", + "HotPatcherCore", + "HotPatcherEditor" + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + // ... add private dependencies that you statically link with here ... + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + + PublicDefinitions.AddRange(new string[] + { + "MOD_NAME=TEXT(\"GameFeaturePacker\")", + "MOD_VERSION=1.0", + "IS_INTERNAL_MODE=true" + }); + } +} diff --git a/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Private/GameFeature/GameFeatureProxy.cpp b/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Private/GameFeature/GameFeatureProxy.cpp new file mode 100644 index 00000000..890cf145 --- /dev/null +++ b/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Private/GameFeature/GameFeatureProxy.cpp @@ -0,0 +1,85 @@ +#include "GameFeature/GameFeatureProxy.h" + +#include + + +#include "CreatePatch/PatcherProxy.h" + +bool UGameFeatureProxy::DoExport() +{ + PatchSettings = MakeShareable(new FExportPatchSettings); + for(const auto& FeatureName:GetSettingObject()->FeatureNames) + { + if(GetSettingObject()->IsSaveConfig()) + { + FString SaveToFile = FPaths::Combine(GetSettingObject()->GetSaveAbsPath(),FString::Printf(TEXT("%s_GameFeatureConfig.json"),*FeatureName)); + FString SerializedJsonStr; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(*GetSettingObject(),SerializedJsonStr); + FFileHelper::SaveStringToFile(SerializedJsonStr, *SaveToFile); + } + + // make patch setting + { + PatchSettings->bByBaseVersion = false; + PatchSettings->VersionId = FeatureName; + FDirectoryPath FeaturePluginPath; + FeaturePluginPath.Path = FString::Printf(TEXT("/%s"),*PatchSettings->VersionId); + + PatchSettings->GetAssetScanConfigRef().AssetIncludeFilters.Add(FeaturePluginPath); + + FPlatformExternAssets PlatformExternAssets; + { + PlatformExternAssets.TargetPlatform = ETargetPlatform::AllPlatforms; + FExternFileInfo FeaturePlugin; + + if(UFlibPatchParserHelper::GetPluginPakPathByName(PatchSettings->VersionId,FeaturePlugin.FilePath.FilePath,FeaturePlugin.MountPath)) + { + FeaturePlugin.Type = EPatchAssetType::NEW; + PlatformExternAssets.AddExternFileToPak.Add(FeaturePlugin); + + TSharedPtr Plugin = IPluginManager::Get().FindPlugin(FeatureName); + if(Plugin) + { + for(const auto& NonContentDir:GetSettingObject()->NonContentDirs) + { + FString PluginConfigDir = FPaths::ConvertRelativePathToFull(FPaths::Combine(Plugin->GetBaseDir(),NonContentDir)); + if(FPaths::DirectoryExists(PluginConfigDir)) + { + FExternDirectoryInfo ConfigDir; + ConfigDir.DirectoryPath.Path = PluginConfigDir; + ConfigDir.MountPoint = FPaths::Combine(Plugin->GetBaseDir(),NonContentDir); + PlatformExternAssets.AddExternDirectoryToPak.Add(ConfigDir); + } + } + } + } + } + PatchSettings->AddExternAssetsToPlatform.Add(PlatformExternAssets); + PatchSettings->bCookPatchAssets = GetSettingObject()->bCookPatchAssets; + + { + PatchSettings->SerializeAssetRegistryOptions = GetSettingObject()->SerializeAssetRegistryOptions; + PatchSettings->SerializeAssetRegistryOptions.AssetRegistryMountPointRegular = FString::Printf(TEXT("%s[%s]"),AS_PLUGINDIR_MARK,*FeatureName); + PatchSettings->SerializeAssetRegistryOptions.AssetRegistryNameRegular = FString::Printf(TEXT("AssetRegistry.bin")); + } + { + PatchSettings->CookShaderOptions = GetSettingObject()->CookShaderOptions; + PatchSettings->CookShaderOptions.bSharedShaderLibrary = true; + PatchSettings->CookShaderOptions.bNativeShader = true; + PatchSettings->CookShaderOptions.ShaderLibMountPointRegular = FString::Printf(TEXT("%s[%s]"),AS_PLUGINDIR_MARK,*FeatureName); + } + PatchSettings->IoStoreSettings = GetSettingObject()->IoStoreSettings; + PatchSettings->EncryptSettings = GetSettingObject()->EncryptSettings; + PatchSettings->PakTargetPlatforms.Append(GetSettingObject()->TargetPlatforms); + PatchSettings->SavePath.Path = GetSettingObject()->GetSaveAbsPath(); + PatchSettings->bStorageNewRelease = false; + PatchSettings->bStorageConfig = true; + } + PatcherProxy = NewObject(); + PatcherProxy->AddToRoot(); + PatcherProxy->Init(PatchSettings.Get()); + PatcherProxy->DoExport(); + + } + return Super::DoExport(); +} diff --git a/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Private/GameFeaturePackerEditor.cpp b/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Private/GameFeaturePackerEditor.cpp new file mode 100644 index 00000000..3eb20aae --- /dev/null +++ b/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Private/GameFeaturePackerEditor.cpp @@ -0,0 +1,35 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "GameFeaturePackerEditor.h" +#include "Slate/SGameFeaturePackageWidget.h" + +#define LOCTEXT_NAMESPACE "FGameFeaturePackerEditorModule" + +void FGameFeaturePackerEditorModule::StartupModule() +{ + FHotPatcherModBaseModule::StartupModule(); +} + +void FGameFeaturePackerEditorModule::ShutdownModule() +{ + FHotPatcherModBaseModule::ShutdownModule(); +} + +FHotPatcherModDesc FGameFeaturePackerEditorModule::GetModDesc() const +{ + FHotPatcherModDesc TmpModDesc; + TmpModDesc.ModName = MOD_NAME; + TmpModDesc.CurrentVersion = MOD_VERSION; + TmpModDesc.bIsBuiltInMod = IS_INTERNAL_MODE; + + TArray ActionDescs; + TmpModDesc.ModActions.Emplace( + TEXT("Patcher"),MOD_NAME,TEXT("ByGameFeature"), TEXT("Create an Game Feature Package."), + CREATE_ACTION_WIDGET_LAMBDA(SGameFeaturePackageWidget,TEXT("ByGameFeature")),0 + ); + return TmpModDesc; +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FGameFeaturePackerEditorModule, GameFeaturePackerEditor) \ No newline at end of file diff --git a/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Private/Slate/SGameFeaturePackageWidget.cpp b/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Private/Slate/SGameFeaturePackageWidget.cpp new file mode 100644 index 00000000..329c727f --- /dev/null +++ b/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Private/Slate/SGameFeaturePackageWidget.cpp @@ -0,0 +1,283 @@ +#include "SGameFeaturePackageWidget.h" +#include "FlibPatchParserHelper.h" +#include "FlibHotPatcherCoreHelper.h" +#include "FlibHotPatcherEditorHelper.h" +#include "HotPatcherEditor.h" +#include "GameFeature/FGameFeaturePackagerSettings.h" +#include "CreatePatch/PatcherProxy.h" +#include "CreatePatch/SHotPatcherPatchWidget.h" +#include "GameFeature/GameFeatureProxy.h" +#include "Interfaces/IPluginManager.h" +#include "Kismet/KismetSystemLibrary.h" +#include "Kismet/KismetTextLibrary.h" +#include "Misc/EngineVersionComparison.h" + +#if !UE_VERSION_OLDER_THAN(5,1,0) + typedef FAppStyle FEditorStyle; +#endif + +#define LOCTEXT_NAMESPACE "SHotPatcherGameFeaturePackager" + +void SGameFeaturePackageWidget::Construct(const FArguments& InArgs, + TSharedPtr InCreateModel) +{ + GameFeaturePackagerSettings = MakeShareable(new FGameFeaturePackagerSettings); + CreateExportFilterListView(); + + SetContext(InCreateModel); + + ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .VAlign(VAlign_Center) + [ + SettingsView->GetWidget()->AsShared() + ] + ] + + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0, 8.0, 0.0, 0.0) + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Right) + .Padding(4, 4, 10, 4) + [ + SNew(SButton) + .Text(LOCTEXT("GenerateGameFeature", "Generate GameFeature")) + .OnClicked(this,&SGameFeaturePackageWidget::DoGameFeaturePackager) + .IsEnabled(this,&SGameFeaturePackageWidget::CanGameFeaturePackager) + .ToolTipText(this,&SGameFeaturePackageWidget::GetGenerateTooltipText) + ] + ]; +} + +void SGameFeaturePackageWidget::ImportConfig() +{ + UE_LOG(LogHotPatcher, Log, TEXT("Import Game Feature Packager Config")); + TArray Files = this->OpenFileDialog(); + if (!Files.Num()) return; + + FString LoadFile = Files[0]; + + FString JsonContent; + if (UFlibAssetManageHelper::LoadFileToString(LoadFile, JsonContent)) + { + // UFlibHotPatcherCoreHelper::DeserializeReleaseConfig(ExportReleaseSettings, JsonContent); + THotPatcherTemplateHelper::TDeserializeJsonStringAsStruct(JsonContent,*GameFeaturePackagerSettings); + SettingsView->GetDetailsView()->ForceRefresh(); + } +} + +void SGameFeaturePackageWidget::ExportConfig() const +{ + UE_LOG(LogHotPatcher, Log, TEXT("Export Game Feature Packager Config")); + TArray Files = this->SaveFileDialog(); + + if (!Files.Num()) return; + + FString SaveToFile = Files[0].EndsWith(TEXT(".json")) ? Files[0] : Files[0].Append(TEXT(".json")); + + if (GameFeaturePackagerSettings) + { + FString SerializedJsonStr; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(*GameFeaturePackagerSettings,SerializedJsonStr); + if (FFileHelper::SaveStringToFile(SerializedJsonStr, *SaveToFile)) + { + FText Msg = LOCTEXT("SavedGameFeatureConfigMas", "Successd to Export the Game Feature Packager Config."); + UFlibHotPatcherEditorHelper::CreateSaveFileNotify(Msg, SaveToFile); + } + } +} + +void SGameFeaturePackageWidget::ResetConfig() +{ + UE_LOG(LogHotPatcher, Log, TEXT("Reset Game Feature Packager Config")); + FString DefaultSettingJson; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(*FGameFeaturePackagerSettings::Get(),DefaultSettingJson); + THotPatcherTemplateHelper::TDeserializeJsonStringAsStruct(DefaultSettingJson,*GameFeaturePackagerSettings); + SettingsView->GetDetailsView()->ForceRefresh(); +} + +// #if ENGINE_GAME_FEATURE +// #include "GameFeaturesSubsystem.h" +// #endif +void SGameFeaturePackageWidget::DoGenerate() +{ +#if ENGINE_GAME_FEATURE + if(GetConfigSettings()->bAutoLoadFeaturePlugin) + { + // FString FeatureName = GetConfigSettings()->FeatureName; + FString OutPluginURL; + UWorld* World = GEditor->GetEditorWorldContext().World(); + + for(const auto& FeatureName:GetConfigSettings()->FeatureNames) + { + if(World) + { + auto GameFeatureFounder = [](const FString& FeatureName) + { + bool bFound = false; + FString FeaturePluginDir = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectPluginsDir(),TEXT("GameFeatures"),FeatureName)); + FString FeatureUPluginPath = FPaths::Combine(FeaturePluginDir,FString::Printf(TEXT("%s.uplugin"),*FeatureName)); + + if(FPaths::DirectoryExists(FeaturePluginDir) && FPaths::FileExists(FeatureUPluginPath)) + { + bFound = true; + } + return bFound; + }; + + if(IPluginManager::Get().FindPlugin(TEXT("GameFeatures")).IsValid() && + IPluginManager::Get().FindPlugin(TEXT("ModularGameplay")).IsValid() + ) + { + if(GameFeatureFounder(FeatureName)) + { + UKismetSystemLibrary::ExecuteConsoleCommand(World,FString::Printf(TEXT("LoadGameFeaturePlugin %s"),*FeatureName)); + UKismetSystemLibrary::ExecuteConsoleCommand(World,FString::Printf(TEXT("DeactivateGameFeaturePlugin %s"),*FeatureName)); + } + } + else + { + UE_LOG(LogHotPatcher,Warning,TEXT("GameFeatures or ModularGameplay is not Enabled!")); + } + } + } + TArray FeatureNamesTemp = GetConfigSettings()->FeatureNames; + for(const auto& FeatureName:FeatureNamesTemp) + { + if(!IPluginManager::Get().FindPlugin(FeatureName)) + { + GetConfigSettings()->FeatureNames.Remove(FeatureName); + UE_LOG(LogHotPatcher,Error,TEXT("%s load faild, %s"),*FeatureName); + } + } + FeaturePackager(); + // UGameFeaturesSubsystem::Get().GetPluginURLForBuiltInPluginByName(FeatureName,OutPluginURL); + // UGameFeaturesSubsystem::Get().LoadGameFeaturePlugin(OutPluginURL,FGameFeaturePluginDeactivateComplete::CreateLambda([this](const UE::GameFeatures::FResult& InStatus) + // { + // if(InStatus.IsValid() && !InStatus.HasError()) + // { + // FeaturePackager(); + // } + // else + // { + // UE_LOG(LogHotPatcher,Error,TEXT("Package Feature %s faild, %s"),*GetConfigSettings()->FeatureName,**InStatus.TryGetError()); + // } + // })); + } +#endif +} + +void SGameFeaturePackageWidget::FeaturePackager() +{ + if(!GetConfigSettings()->IsStandaloneMode()) + { + UGameFeatureProxy* GameFeatureProxy = NewObject(); + GameFeatureProxy->AddToRoot(); + GameFeatureProxy->Init(GetConfigSettings()); + GameFeatureProxy->DoExport(); + } + else + { + FString CurrentConfig; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(*GetConfigSettings(),CurrentConfig); + FString SaveConfigTo = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectSavedDir(),TEXT("HotPatcher"),FString::Printf(TEXT("GameFeatureConfig.json")))); + FFileHelper::SaveStringToFile(CurrentConfig,*SaveConfigTo); + FString MissionCommand = FString::Printf(TEXT("\"%s\" -run=HotPlugin -config=\"%s\" %s"),*UFlibPatchParserHelper::GetProjectFilePath(),*SaveConfigTo,*GetConfigSettings()->GetCombinedAdditionalCommandletArgs()); + UE_LOG(LogHotPatcher,Log,TEXT("HotPatcher %s Mission: %s %s"),*GetMissionName(),*UFlibHotPatcherCoreHelper::GetUECmdBinary(),*MissionCommand); + FHotPatcherEditorModule::Get().RunProcMission(UFlibHotPatcherCoreHelper::GetUECmdBinary(),MissionCommand,GetMissionName()); + } +} + +FText SGameFeaturePackageWidget::GetGenerateTooltipText() const +{ + FString FinalString; + struct FStatus + { + FStatus(bool InMatch,const FString& InDisplay):bMatch(InMatch) + { + Display = FString::Printf(TEXT("%s:%s"),*InDisplay,InMatch?TEXT("true"):TEXT("false")); + } + FString GetDisplay()const{return Display;} + bool bMatch; + FString Display; + }; + TArray AllStatus; + AllStatus.Emplace(HasValidConfig(),TEXT("HasValidGameFeatureConfig")); + bool bHasSavePath = GameFeaturePackagerSettings->GetSaveAbsPath().IsEmpty()?false:FPaths::DirectoryExists(GameFeaturePackagerSettings->GetSaveAbsPath()); + AllStatus.Emplace(bHasSavePath,TEXT("HasSavePath")); + + for(const auto& Status:AllStatus) + { + FinalString+=FString::Printf(TEXT("%s\n"),*Status.GetDisplay()); + } + return UKismetTextLibrary::Conv_StringToText(FinalString); +} + +// #include "DetailsCustomization/CustomGameFeatursDetails.h" + +void SGameFeaturePackageWidget::CreateExportFilterListView() +{ + // Create a property view + FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked("PropertyEditor"); + + FDetailsViewArgs DetailsViewArgs; + { + DetailsViewArgs.bAllowSearch = true; + DetailsViewArgs.bHideSelectionTip = true; + DetailsViewArgs.bLockable = false; + DetailsViewArgs.bSearchInitialKeyFocus = true; + DetailsViewArgs.bUpdatesFromSelection = false; + DetailsViewArgs.NotifyHook = nullptr; + DetailsViewArgs.bShowOptions = true; + DetailsViewArgs.bShowModifiedPropertiesOption = false; + DetailsViewArgs.bShowScrollBar = false; + DetailsViewArgs.bShowOptions = true; + DetailsViewArgs.bUpdatesFromSelection= true; + } + + FStructureDetailsViewArgs StructureViewArgs; + { + StructureViewArgs.bShowObjects = true; + StructureViewArgs.bShowAssets = true; + StructureViewArgs.bShowClasses = true; + StructureViewArgs.bShowInterfaces = true; + } + + SettingsView = EditModule.CreateStructureDetailView(DetailsViewArgs, StructureViewArgs, nullptr); + FStructOnScope* Struct = new FStructOnScope(FGameFeaturePackagerSettings::StaticStruct(), (uint8*)GameFeaturePackagerSettings.Get()); + // SettingsView->GetOnFinishedChangingPropertiesDelegate().AddRaw(ExportReleaseSettings.Get(),&FGameFeaturePackagerSettings::OnFinishedChangingProperties); + // SettingsView->GetDetailsView()->RegisterInstancedCustomPropertyLayout(FGameFeaturePackagerSettings::StaticStruct(),FOnGetDetailCustomizationInstance::CreateStatic(&FReleaseSettingsDetails::MakeInstance)); + // SettingsView->GetDetailsView()->RegisterInstancedCustomPropertyTypeLayout(FGameFeaturePackagerSettings::StaticStruct()->GetFName(),FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FCustomGameFeaturePackagerSettingsDetails::MakeInstance)); + SettingsView->SetStructureData(MakeShareable(Struct)); +} + +bool SGameFeaturePackageWidget::CanGameFeaturePackager() const +{ + bool bHasSavePath = GameFeaturePackagerSettings->GetSaveAbsPath().IsEmpty()?false:FPaths::DirectoryExists(GameFeaturePackagerSettings->GetSaveAbsPath()); + return HasValidConfig() && bHasSavePath; +} + +bool SGameFeaturePackageWidget::HasValidConfig() const +{ + bool bHasTarget = !!GetConfigSettings()->TargetPlatforms.Num(); + return bHasTarget; +} + +FReply SGameFeaturePackageWidget::DoGameFeaturePackager() +{ + DoGenerate(); + return FReply::Handled(); +} + + + +#undef LOCTEXT_NAMESPACE diff --git a/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Private/Slate/SGameFeaturePackageWidget.h b/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Private/Slate/SGameFeaturePackageWidget.h new file mode 100644 index 00000000..8c7f94a4 --- /dev/null +++ b/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Private/Slate/SGameFeaturePackageWidget.h @@ -0,0 +1,60 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Model/FPatchersModeContext.h" +#include "SHotPatcherWidgetBase.h" +#include "GameFeature/FGameFeaturePackagerSettings.h" + +// engine header +#include "Templates/SharedPointer.h" +#include "IStructureDetailsView.h" + +/** + * Implements the cooked platforms panel. + */ +class SGameFeaturePackageWidget + : public SHotPatcherWidgetBase +{ +public: + + SLATE_BEGIN_ARGS(SGameFeaturePackageWidget) { } + SLATE_END_ARGS() + +public: + + /** + * Constructs the widget. + * + * @param InArgs The Slate argument list. + */ + void Construct( const FArguments& InArgs,TSharedPtr InCreateModel); + +public: + virtual void ImportConfig(); + virtual void ImportProjectConfig(){}; + virtual void ExportConfig()const; + virtual void ResetConfig(); + virtual void DoGenerate(); + + virtual FGameFeaturePackagerSettings* GetConfigSettings() override{return GameFeaturePackagerSettings.Get();}; + virtual FGameFeaturePackagerSettings* GetConfigSettings()const {return GameFeaturePackagerSettings.Get();}; + + virtual FString GetMissionName() override{return TEXT("Game Feature Packager");} + virtual FText GetGenerateTooltipText() const override; +protected: + void CreateExportFilterListView(); + bool CanGameFeaturePackager()const; + bool HasValidConfig()const; + FReply DoGameFeaturePackager(); + void FeaturePackager(); + +private: + + // TSharedPtr mCreatePatchModel; + TSharedPtr PatchSettings; + /** Settings view ui element ptr */ + TSharedPtr SettingsView; + TSharedPtr GameFeaturePackagerSettings; +}; + diff --git a/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Public/GameFeature/FGameFeaturePackagerSettings.h b/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Public/GameFeature/FGameFeaturePackagerSettings.h new file mode 100644 index 00000000..5755d4cb --- /dev/null +++ b/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Public/GameFeature/FGameFeaturePackagerSettings.h @@ -0,0 +1,73 @@ +#pragma once + +#include "HotPatcherLog.h" +#include "CreatePatch/HotPatcherSettingBase.h" +#include "FlibPatchParserHelper.h" +#include "HotPatcherLog.h" +#include "CreatePatch/FExportPatchSettings.h" + +// engine header +#include "Misc/FileHelper.h" +#include "CoreMinimal.h" +#include "ETargetPlatform.h" +#include "UObject/ObjectMacros.h" +#include "UObject/Object.h" +#include "Engine/EngineTypes.h" +#include "Kismet/KismetStringLibrary.h" +#include "Serialization/JsonSerializer.h" +#include "Serialization/JsonWriter.h" + +#include "FGameFeaturePackagerSettings.generated.h" + +/** Singleton wrapper to allow for using the setting structure in SSettingsView */ +USTRUCT(BlueprintType) +struct GAMEFEATUREPACKEREDITOR_API FGameFeaturePackagerSettings:public FHotPatcherSettingBase +{ + GENERATED_USTRUCT_BODY() +public: + FGameFeaturePackagerSettings() + { + SerializeAssetRegistryOptions.bSerializeAssetRegistry = true; + NonContentDirs.AddUnique(TEXT("Config")); + } + virtual ~FGameFeaturePackagerSettings(){}; + + FORCEINLINE static FGameFeaturePackagerSettings* Get() + { + static FGameFeaturePackagerSettings StaticIns; + + return &StaticIns; + } + + UPROPERTY(EditAnywhere) + TArray FeatureNames; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bAutoLoadFeaturePlugin = true; + // Config/ Script/ etc. + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TArray NonContentDirs; + /* + * Cook Asset in current patch + * shader code gets saved inline inside material assets + * bShareMaterialShaderCode as false + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Base") + bool bCookPatchAssets = true; + + // UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Base", meta=(EditCondition = "bCookPatchAssets")) + FCookShaderOptions CookShaderOptions; + // UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Base", meta=(EditCondition = "bCookPatchAssets")) + FAssetRegistryOptions SerializeAssetRegistryOptions; + + // support UE4.26 later + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Base") + FIoStoreSettings IoStoreSettings; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Base") + FPakEncryptSettings EncryptSettings; + + UPROPERTY(EditAnywhere, Category="Base") + TArray TargetPlatforms; +}; + diff --git a/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Public/GameFeature/GameFeatureProxy.h b/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Public/GameFeature/GameFeatureProxy.h new file mode 100644 index 00000000..8d393a86 --- /dev/null +++ b/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Public/GameFeature/GameFeatureProxy.h @@ -0,0 +1,24 @@ +#pragma once +#include "ThreadUtils/FThreadUtils.hpp" +#include "FGameFeaturePackagerSettings.h" +// engine header +#include "Templates/SharedPointer.h" +#include "CreatePatch/HotPatcherProxyBase.h" +#include "GameFeatureProxy.generated.h" + + +UCLASS() +class GAMEFEATUREPACKEREDITOR_API UGameFeatureProxy:public UHotPatcherProxyBase +{ + GENERATED_BODY() +public: + virtual bool DoExport() override; + virtual FGameFeaturePackagerSettings* GetSettingObject()override{ return (FGameFeaturePackagerSettings*)Setting; } + +protected: + UPROPERTY() + UPatcherProxy* PatcherProxy; +private: + TSharedPtr PatchSettings; + TSharedPtr ThreadWorker; +}; diff --git a/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Public/GameFeaturePackerEditor.h b/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Public/GameFeaturePackerEditor.h new file mode 100644 index 00000000..7605ebb0 --- /dev/null +++ b/Mods/GameFeaturePacker/Source/GameFeaturePackerEditor/Public/GameFeaturePackerEditor.h @@ -0,0 +1,18 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "HotPatcherActionManager.h" +#include "HotPatcherModBaseModule.h" +#include "Modules/ModuleManager.h" + +class FGameFeaturePackerEditorModule : public FHotPatcherModBaseModule +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + virtual FHotPatcherModDesc GetModDesc()const override; +}; From 71ac57bb761eb739bba816cae61d953fc7fdf369 Mon Sep 17 00:00:00 2001 From: hxhb Date: Wed, 4 Jan 2023 09:26:34 +0000 Subject: [PATCH 20/81] add HDiffPatchUE Mod --- .gitmodules | 3 +++ Mods/HDiffPatchUE | 1 + 2 files changed, 4 insertions(+) create mode 160000 Mods/HDiffPatchUE diff --git a/.gitmodules b/.gitmodules index 69d7a302..c63f7eaa 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "Mods/HotChunker"] path = Mods/HotChunker url = git@github.com:hxhb/HotChunker.git +[submodule "Mods/HDiffPatchUE"] + path = Mods/HDiffPatchUE + url = git@github.com:hxhb/HDiffPatchUE.git diff --git a/Mods/HDiffPatchUE b/Mods/HDiffPatchUE new file mode 160000 index 00000000..4c2c4145 --- /dev/null +++ b/Mods/HDiffPatchUE @@ -0,0 +1 @@ +Subproject commit 4c2c41454bd3cf14d79201e44a1a8a089cfaf375 From f08f8816ee75f26631de6aced1b7fa4547db64e2 Mon Sep 17 00:00:00 2001 From: hxhb Date: Wed, 4 Jan 2023 09:27:45 +0000 Subject: [PATCH 21/81] update version to v80 --- HotPatcher/Source/HotPatcherCore/HotPatcherCore.Build.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HotPatcher/Source/HotPatcherCore/HotPatcherCore.Build.cs b/HotPatcher/Source/HotPatcherCore/HotPatcherCore.Build.cs index 5538cf65..2e86b4c7 100644 --- a/HotPatcher/Source/HotPatcherCore/HotPatcherCore.Build.cs +++ b/HotPatcher/Source/HotPatcherCore/HotPatcherCore.Build.cs @@ -189,7 +189,7 @@ public HotPatcherCore(ReadOnlyTargetRules Target) : base(Target) PublicDefinitions.AddRange(new string[] { "TOOL_NAME=\"HotPatcher\"", - "CURRENT_VERSION_ID=79", + "CURRENT_VERSION_ID=80", "CURRENT_PATCH_ID=0", "REMOTE_VERSION_FILE=\"https://imzlp.com/opensource/version.json\"" }); From e15b7e30fdbd1a20b0c3116f7d7a04b6430d562f Mon Sep 17 00:00:00 2001 From: hxhb Date: Wed, 4 Jan 2023 09:42:04 +0000 Subject: [PATCH 22/81] optimize impl --- .../Private/SVersionUpdater/SVersionUpdaterWidget.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/SVersionUpdaterWidget.cpp b/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/SVersionUpdaterWidget.cpp index edea2d69..142d3bc2 100644 --- a/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/SVersionUpdaterWidget.cpp +++ b/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/SVersionUpdaterWidget.cpp @@ -300,7 +300,8 @@ void SVersionUpdaterWidget::Construct(const FArguments& InArgs) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() - .VAlign(VAlign_Fill) + .VAlign(VAlign_Center) + .HAlign(HAlign_Fill) .Padding(20,0,20,0) [ SAssignNew(ChildModBox,SVerticalBox) From fee0a618e9b66776c34e3d5d06b546a341e602b0 Mon Sep 17 00:00:00 2001 From: hxhb Date: Wed, 4 Jan 2023 09:53:08 +0000 Subject: [PATCH 23/81] fix Property Category issue --- .../Public/CreatePatch/FExportPatchSettings.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/FExportPatchSettings.h b/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/FExportPatchSettings.h index 9a9e052e..2693367a 100644 --- a/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/FExportPatchSettings.h +++ b/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/FExportPatchSettings.h @@ -222,15 +222,15 @@ struct HOTPATCHERRUNTIME_API FExportPatchSettings:public FHotPatcherSettingBase TArray ReplacePakListTexts; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options") TArray PakTargetPlatforms; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options|Regular") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options") bool bCustomPakNameRegular = false; // Can use value: {VERSION} {BASEVERSION} {CHUNKNAME} {PLATFORM} - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options|Regular",meta=(EditCondition = "bCustomPakNameRegular")) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options",meta=(EditCondition = "bCustomPakNameRegular")) FString PakNameRegular = TEXT("{VERSION}_{CHUNKNAME}_{PLATFORM}_001_P"); - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options|Regular") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options") bool bCustomPakSaveDirRegular = false; // Can use value: {VERSION} {BASEVERSION} {CHUNKNAME} {PLATFORM} - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options|Regular",meta=(EditCondition = "bCustomPakSaveDirRegular")) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options",meta=(EditCondition = "bCustomPakSaveDirRegular")) FString PakSaveDirRegular = TEXT("{CHUNKNAME}/{PLATFORM}"); UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SaveTo") From 24f0e86847d3951f95fb6b20a6d3c3a5de26bc97 Mon Sep 17 00:00:00 2001 From: hxhb Date: Thu, 5 Jan 2023 04:12:12 +0000 Subject: [PATCH 24/81] update Mod --- HotPatcher/Resources/Payments/alipay.png | Bin 0 -> 74271 bytes .../Resources/{ => Payments}/wechatpay.png | Bin .../Private/HotPatcherStyle.cpp | 138 +++---- .../SVersionUpdater/SVersionUpdaterWidget.cpp | 343 +++++++++++------- .../SVersionUpdater/SVersionUpdaterWidget.h | 48 ++- .../SVersionUpdater/VersionUpdaterStyle.cpp | 11 +- 6 files changed, 324 insertions(+), 216 deletions(-) create mode 100644 HotPatcher/Resources/Payments/alipay.png rename HotPatcher/Resources/{ => Payments}/wechatpay.png (100%) diff --git a/HotPatcher/Resources/Payments/alipay.png b/HotPatcher/Resources/Payments/alipay.png new file mode 100644 index 0000000000000000000000000000000000000000..a2c5d2e123adb55caa0887805aae9568a2dc559f GIT binary patch literal 74271 zcmXt<19V*7*T#d1(b%@tG-_-$c4IfT&BnHE+qTuTv28owegA8Hv(|9U+?g}yp1q&F zpWg{nkdr`y$Abq014ELM6jcHP1J?kq>aY;NE9M$~{lFcZousBC7#Q-ve^>Ak+frjN zFcL5+Q6Uw#%(E;_7jhYgVm=5+RuqDG?lmQul9b}*4fV_7l)0kv@lNhourOTIjW0QG z%U`~T#*P|q&^OH!xe1a$yE-~LW>o!V4MBNAzz{?A>0^F^`pn9GYk;{z@aZE4_gQ=o zpt}1!1AqI3wgx@QOusB_r~F+7@4*4yX$!ssQR{cMsdpvw?Fp)93;Qh|)~C<+%c$Ik zUVtDp@E-aBX82cmgr4MLNS`w3o~<6Jw;hP}*>TwN@*QS{<@?iajhVG`7q8u#t@D>- zJFc@wPw$?8R!^KkC`M~m9A3IL%O9CIMyjed^ao$RXfmaEwXNH{Gp##t5p>(?dhBl7 zZRxzecRx3llu+m3J)YdUg1KFB!+#}Q{>sY_yX~bPv<+3d<=pGhb?`8-jbd~sm~?S% zH)YXuv%Tc3v+T^imC+o!$e8=fLQ+nca3HX>pVb>)aR$Ow$mnee^ei>AX~j`Wtv zPsVRaNSM>tf*c-LMtnKUROBEsi$^BYFp`RVDYd=CHl z<15bY?k>5=icQN4zL9p#^47F~h}Vm*^7*|-CsR=GDSH9%8Q_AmYtyp(vhOgKz1XXZ z+@&j@6OoUD2u(`*e0&U7-M)EQvOo(-GjVfp5OV6QX%$)zl{%--vK7_q#B+{AhrVnE zU8}*swg_iyZRhG*GGn6o;K66go-yUo1^j{d$f2kAT3*`}eqL%`|Gi*Iz0fbn%mXKe z5M*@n7#cHT#J{$-=Jo!9sfNtx{qgqv^1{<0i-BY0<>oe9D<6sONAa6e#G*# zgBawiQnT#H6It@$Nl!jX^(A~{*B(EACODSnY-a}|Qd?Iy1m)%3hvVqo=khmJm>^HZ zlJxHKqqELwJupjgo;{mpwvVsx6B9C$X6$&ugE9B{_Z~W`sxSsk{#aWN56RpQukYpM zIA1dS6Y|VcYg{@At@5i+MGzVpI}eUdF{4{zA#q?yJKb+XeufN?8b{R*`+eS|^aP<) zS68oaWTSB5AT}oE6AZK9AZm~k`Jm;q8dep{O{jx|q-I+;FS$RrmD@vFL>w5Beee*W zWkW9yZ@C9P9?pN_V}o&IAf1s=d|;woKYH*T)zZMvK6Sm)!ekfU2MbxoWRAdA*VN!% zxbaROosAf95hRu;C*$P)HA~(g(DGDWI_EQ(IF;F zy^{9kt=!Sx?$EY%bAvX&&74V3;l91IlTQQR{6iK-as+On`KmL*y>39FAg(kIlH!}T zj*kE8k_BZ*QCMS_I=(LTp%q6U{!Pk4OrI+P)Z^ph%jvDjEs3@h@oRf4+`zVi1fPQH zk{wqN!f)?a5>2S?LkKxqJ{TU&n&rylhTJgoqQ>W}8$AS)z}VkjJXscdhpZ6EEI6*d z&QJ#Ic%&`nl%jlykS_G?pxyYlv{YnYr0lOq>84Cfv{B;ZriE}JqGbZpuo@G`PFfgH zz5~}Bqc}ob#r>&nLaSr{t}U#^?5}JTl|$Vn4a-?*t%6W8H97t98xFxH3DXT;Mm4F0 zR^=m}+Ibu`lS$M>yVq?d>WxLDzb9-#Q;x<4d4(#}YBMS35)l(q4mI|GIxVWjNA~KS!ovU-~c=_eqf|prfsAn+t8o^)fm2X;<&_MV*~|jx<1U zvsWyT>TboKI%4bbwX1=VUuILADTVc4-dE+g;{?b8e~K^*YM<4Tej-ThWpJ9 zaWhQ13Wyf}FBfDAc;F2LuDeF|Y+g>_P&ggK`ItO9$_d9s3Q>OsN7%nDnle_X6XVTT1#xnu-}b zSkiur9PR7`gvwGS?EGc@wA#;0R-pvDNSTg8@Mj?FDM|hZ?g4M{+k^k&CZ3_Cw zm3^PJg?4NE*S2N|_70`?d4B?13S^D{i;KYL`G%6H!QfHndb@BmNw(|M(-Y3ThZL0u*GIZd`$jCpd5D8PZv@@3uAO@w)Jf@_iWK4-`2}-gSgbw5}$Y5Q6A9h4* zr|hG$YWev`Jr91ft=l>?QZECoPwFeUS0mDiF}#Dp(-LKX8-c=F)a>8t=}I;Fs0P|; z^KP5YQ4)vmb-~wI9SjV8#@3ME>8k$I0DQX&#$qISuqY5KEvR6$ikj-5k z3msB6Y-xg(@yLa;&XS-$*}PITGbxb6&Q;CeE5nmR&!#075I>yjQLoh3gv?Wt)Xm?7 z!nR=YgdCcuL`W2%B79!&j;Qk{UfmsuMGP78WVzt$Qzwjh z@h_M4Pa}qv(0QEP9?sm1eLIYR++Qe>Cj~WwW7FvVh}^okuxZmqGsTaeoIkw>ay7Mp zsG4dLuLfb@;WIEDf9+j6*3Zw+^;zuXg;Z5LE6U5)ZCrhQzidw*Iud=fZyQuBS+uN5 zO~1T9ZhmJPCL7gkU$@E2jHZ>RLVx_w)zMMiOp5Cp9zb_;c5d3dOqnoI#F*H>+1c3v z)DTHeJ`IIklz73ugOtj?FXBmTs9x;i}e)I^Kw zCsds_b4X;i0lj*?vCQrmq07bIsk6v>@!xC(P?S_Pjsf#|)8?$bZ$fo-XIK-SGN&!(II-kXqAIACn3eBMmNLur^XAtIp?iw;NV+{xn>Q)Qm}Iq{uy%$XL-82+oQ&kR9IAy zEjQ$n!=#^3GJ3Tazaq3TVnm;L4#e?adxhWq(u}#9%OKO7Z2s7UKz!(W?JFchYj_wLDM59aoeS_;me zwSL^eh=a>UAfiFs90Z=L7Cmk4MB2%=DAcP)Iuq+WjLbDeX7FcwHX~`-WGzaFL|Q{7 zrJu5I!UbKet@>9Uqd8=#=4s{SH%B<{FKFUdkx_mT0jS<-I~waxn|$D0uoebclLCOG zbN^h+Tea+(D>`jwQp!cHO9;XzM26Y?Mc9ZVESQdzELY=fu_MF~A{39*7YUpdX3Pty zKWQhF;Hi(as?@4`;P7qYg!AW~bM_ByTULPrU|_pet7>?c6+LBF0e#Nwx@p};;$X?9 z<@DYIQCFr{U$?c)FucnoHn7i6SyFcVcH^Dtcg^Ny!ygB4*G^8V6lvZAWmfUj+>w}L z?s4{+dhW(AJSlnF|kxPB`nt??X?J~_pYXFD#p zr!^Vk8+7Q0P>ANbIdk?jS+xpGgY&bZ2EPV~vA2VRV*YAjf7~$N({>WXzcl-pAfZ>8n&M!Cznr<3x$qww=2~lsRf zC+`mp4Gjzo+~0rO;cQ7Xf$P{~dpc-yt5zyP-$TA45bk~VFBFk-qG~z+Hm6y z2^&;#fNb1?SVWCF8L7mzP{eqBCtA{PSvAo0e(0J_`pA+i=~$0;He0$=?%C91Nx%x|wLtYd_cZ9g)-9GQ*q5BTU#|FNi zubUZ8`Rc7J#Ag(?%1E7fLq0v9FZZVzoVK-ZUxmf`IwpPRaTt0queJwZK&o+FM+(bp z>+54UXWV5<9fiWu43G3h%aUrJxWJ*YeYL!F@cVpy#Ai?Mf$B#?zf^C#mzuO0l1cFW z^?Fyr;Kxc+cjT|vxH@vjz(S`+y`!X4&J3hgIA3{JjzfKx*7kO#YPm*1#D5TAMT0>q zrh)JT`Eb+p3e=%TZINK9GlyCv+?L`6aWGuIPgmQRxc&+yNbbJAGkw{VxB6JL6AnCC zKpaoA%E``tid?I{e%mGfJkkfbAq~{gBY&-u{Av-T;| zT^mJCugupsj^0UBVVZEmIt|3off~J@@hvtS#NX0nj%k$L-QBuXY9^ZHql{Fl=Uvxs z+RIH}J}7#>H3kf(eu0LhoVdAk>7c|T{Q4tO5xd8OG$#kURHTF&$FA`7ik zUU+pF{d`yqqL0197Xr~=I5PaX9DgiFBar4rwAx#)s7}j>9 z^WWtoA*#NrDkBzx=rvDU6H5BW%W_Y=W>6|m#*|t`k{qN>O0k{0ds!j$WRNY87~N5I z`|j_ZRaI}F-=;D+#T4yI%lG7J$vPv?*A89%qLm+(tT+OV8Eotk_}B2G#GQ2p zNN~i{4Cio&xVGVx*n{$cQ{x(&6tt#KT2-Nh`8Ra*a}tl&4x6pHx<=U)bSd1bkLZ&U zh<)47O?_AYb5G^a^P1d%=s9y)Rh3eaz8GUTpJw**FRu@>KPYxApCpkq&5_D->7<>i zsy8cQM;UFf_EySr6o{qcUum>UX)3r~S&sJ7$7@ zK+Ov!^OVrqOE8*0Jc`}E%sf87*OOiS$*LwAxNpQB?W~FP&=Mzn+k&RIyZiAcy$^M(j$P`(~S#aBOzqY1ebS+qQObPXBSX;nRxxfnG|{q^Km zQC*3U5-bQe)>*DD1B$PIRu8~~bV1#ZQnS6SskjjgA#@f&i&lBkz~AkyQ6ntoWY z{f#)M#bFG}meX3pYZYSmj&*6!)SY!yc#e}d2N8rig><4x*%jt8j%uZ9h6M`_>@%CR z=N_9sn2DyxfiO#TIhg;!ph+j<$RD5XIk{Wbx>sC-b@nO4+ArsH&=3$ZZFR zq(QV$cKUJZA$RuwQ0}auGDG}2WlD|j|2JF!B>e!d((NgBXrlSz=7yJqkpG;KCFxTx ziXz4W$3FD~n_Wmqhv=x72N#$N3fcNA&7>!Wm)|`*kJqh?5PBSN zVh(Hsvj|o~x|!`K^4mAJ)lyH}LLO50-RZ$k;9( zwN-)KBBc7@wk=gGZKtDn;g`;GWu=8~1Ul`}*I_{N-aS2)I<)eJ?i#b;C?x6Uj4O5M z{J}hQV;JhpL~T4T;LwMc;&ud`LJY#z&-~nD=wYwgC8~G zj8FQeRYioGpH=uK23yx|xw(NxPmodxbgh^iBF6uPGnLbrhu*mj4)#vr$he^Mb5{eigXWN2m!QFAt!0`;hwGUDe$KvUj(b`jGjMIyk zL*Crc`~!{}OE5?(EPb%Z1qaD+HX715nA^EBzLk#R?S+kM3hi-~k4hO1k(00uuN5p4R=%C3FzZoJ{}~gbL!XgvLY-|? z3ZEgyFk}c(C*-U?gHF-73s*M)X;YQ`H3QGtDB-942S*TpcgkJNsxx6G)npiZ3K`yw%Ep~Aj-n+PiuvxMWs*A&^E zg@mTB-ak}H{NB$Z0>pZ;b)cDGHID1Py164>)L!~U+x zx-+YD>6{3I!MnQo6RLtQO%pY;#+y9`yYrm8Y?4GfV+fiVM3pUBNcGC_8jUVUNrfAz zGyC1sdL4JCf|BN&m0~8L5NXeXqf&;eDd}&*Lm+80lE?TBr%-Z}K3up<5LQ}I5nb~& z(N<3j&eTDxPf%GLs`Q5j6|)~vk@OF)7F_YH0Mzk%igXQ?=~JLg#$dCPbU(;kEte*U zZ_7y8Q9SgR;u{_v@GP^I;xhH>D?DO|CLsrVACxf(P~>G+P)%Z|%|qB1hCO@%Y!M#S z6q<>6%tR<@sQNIxY01vUrwco)GR4$DbZY~l&8@s>l`9-vnXFK_4>D2rvnOJqcXNZTz_Pq_lVYdhe}LWOKw zUOpbd{7E7S!(A8vi)QlPMfVhEPYKRMgcU6nMrt}kt3(^uUhVmZpLtkvI|QYFf1fh% z=r`PE%QZzWj1gZ`($ZWnPj=1O7ee794C{kt$47PMrbdlLqq~H< zvn$683O&?fb9r`HSj zm`=W4UU&1${2}I5YkXi*DOG9}rb2g9rh#1wc~hjnYV%`?@n$$P#UM|GN@;_X$x0W& ziQ5LB67a~l9`|FtD|oBV20{D!XwXk+lxrCsN57c3AC!sx84B^TgOjGa1b$rER8P+? zHG>)2h4n6{q`h>;wuw3bt9M_!MN;wc9+O|h(4E(jBO~uQpSA1a;O2Q;tw=AUywCS{ zH14r}kKz+WcTs3)C`VZC`JUD8T}(_24qo3qV6cT|fV4tXs6)eti?w;C{7c+19d8Cs zF#IQUDo31n_C-z9^>lO$tR#s3Fqk&{V@kqbPN}_0U6bQ??e5c!(m*yNU8-T20L1i8 z&|?8PRd6P)nL#>Q9&2`tI60yzY0#K)fcH=HZOLstx#@fw!C$fvEb(zQt>VIECi2tv zd~&`g+O$2oer@|ly{mJ5vG}5Flr~-1-7Hy>*urgG0}EzS^2ONvDU+TA-`WojlX4ybNmW&y zSIhpkR|GLzcD%B`ZOW3(RUh7ob?X|E(k`YTi(NtLybN2_5u}KBL9VfOVG*(`OBE_f zs-lT6B!lc{;xD&6UAuU-`&Hb{~$4mF1OD{ z_W9F9({L5L&jcDn;B&%AB1LaA5!MZ;J@pu{csktfDq?WVg{@*Ae))5{$ zG2-$0?|)heL~H`#g^}PJ!2l0?SWHM zQ2L8IEKGB0$5r~Wa(-?-;eRLX9kQM$h2;LPhpU4<#aJD z?ynDw!v64={#1o(bdI4YfKY5-aK09vpcfjiH6^v~kqalrMudjUU&B<4;6WG8Js=~C zVb_hn;=Ze0A5ZtAj6WbS-UYnl)>H?i6_R0^;gRGgpr%In(ZdWL;;&P%dFlK9l5|ae z5GhVBE1c+a!Xhh49WVAgk-|*6kFbq_NXn2hAu>+V-r1=V%r!}KW?ekSIcI^C38kcQ z7d0;K@ZfMIN0o39DpFCeT8FJ7wlngmuBsZex-5mnP}w{VXMTfUhwet5Ql_{Er*5iA z#+DV-d@2G9Waa8jy3RZ8Fu@a=%%_~s=N#MynXNfAw~vRGp0PZ@p-e4!YUk(W=Wh>Y zW~T_iDM*K%J0hbPaL`$n1Tl!Dk^RPSszhPW!Uya=qWInyP2d3L+w>7b z!r(v45p4gT1%RNdRpz9CsH)@#Y*>zWjkh~OG@;1r)fQV>$1YuZ7@-;<)vEC_OP0z@ zft+;`lm|iT6`0dre1{JYYBL7sK<}_ruovjxySr-^C&%7A*qc0ohxHx@>1CY$Bwp1H zkOFP^IbQ8~C6De^etgPp^1_@&twOa-$e92nGXj*QAjGf< zzY{qDQ@(y5FR;3APQK?gvO=(G)u^M((JJ&YjB)Ah*mwGooga};CIMxqyO&pG0*r2h z2;tG=t3~ICxoa7xORe;VbtD*FqUj#rn{F^Ftc=pT6G0N~(eJQRe`;JIEB5#=SU>aT zD>tg=Yv8Dq?fkh84rg+fd|vh=1zFdD_Vx2mQAnE-E4BcXeu4J|0s8`$K$An&Ues){ zJB}7rFOJ-!m{2zERl+%?2lxmuQhn%#s0a6~@Mkb;bxI-_syYt2j`(6>a<&vp?Xrbh zG1h1s8hN4aY@Jho8?way{+3qReq`eDdKCu8p1FjdlfsKATJY)^2RiC>Dh~w2@drns zz2YXjH4WPIBvF&BCsm_}k>tdL(^DM%Ja2D#abpX~cBTH{o@fVXO)M;dV+C-?ldWn4 z`WEU5q-Fj|oN)CN9~+oXWb=y)LPrCRKsE8%;a@AFaU5Ka-0+=qD-i)-+d=F;bCjDA zgz=O+i5T*%0p1qa`;`)xN=;V8ks%n7sCP;R0wvS4xnP{ce`x>F5>PV)CC0ebju(BP z%oRO6PvC;%_adMeRfd_dK~MyY8RuV6;Rx-0tE`Mxj#Gg&)=CTSl35&*>I#mzckn0{5a4rso=FO!nJ{TO8kx0Q?bK>Arhpv-~2telA@w|x?Ll2}nt zNkR1#fyYjptI@)VE5xg2!6yEB>vxaoszzPifZJzjU+kDCd%+h%%K0BdJt;&yA4)I)4+CNZ%?w75ma*dJg z=H>p7q+jOh!Scrd$CQ`I6Mre?D+G3JQf(|Bb+;ets61)vl<6G7fCutg+WFwhAuLyd_=z$kEnJ~{YVcIbCN&dXjFKW2aBDGK2qT`{zc^`FDb{UN z)l^8C0r z6yMrnbL7<{L$Re}{8}y>V)-NLgiw{|FK%05Ui1C_L!UjA;!d#eO`by(L2}<@Xs3)a zmh@@+#!t-SK7BA>_s0vuTTB>SL}&|_dHLXyAoQLm3Ua&h-O2ze-v|Di-X9xG^}KLZ zkz4YhOAg{6C(1V3Nmj4nhXzJREb8eE^{(y#$jQJj|K@N;+QcKQI@@2D$Qlrf7%nd_ zuPwf3D0>$b@w4c=3YrM@s%Zm&5S>|<(J=|13OeTTWY56<*3&P}hH%3oCxPm1=`x(( zM%cXuAQ8{I#O#_Dw{HFB?$U)mcp4g%Q3RAWxPzS?;kI6nvc*Om@{8e@R?|9aSYzDK zK32au&s4_YY%@*y;RdtWbSIV1$&V^RKnP=}5f;XGh|S}@P3AR- z&3L>H&-F4JPV=J@9iS+S#dlXEIFT5SdQ%{s8X+FrCFVaesVGBn6ZGGBZy^Z|(c zfs6KcvGt?Qz4c5AQf9;SkF#@;5$Io_wxw9e1!n#xbR~WSVK!7-5^P5d z1-O;4HOV9;f;RysB?0Fo)ahH1|vNUIPV$iK;PwvwmHYRf%bG9J9yj3-osf z6SHlVSZ0pD?#NvGgN0IIof-Rl4PtlsKgaE7D@2sMFl-MZzbC-n%@;c#mjqZ5%o*oL zpE|aT$V`JQeuX5e=HD5la`x4pB-0L;O`=>sC4e2%NHhO+K^g;%Ce8nm{M>4syX7tz z#l%O1julR`xE#R=8$+U{d5V3pxV8g0xMd1zy84c0c6m?{LYp>1xG$J81~ zg50?X`sqDsO)BI}WKI^D2O36=!U^Z+$-fAPr_a5=d)Fe}w6nSOZ2G#n3CgOmd8&`T zwPg-hIn9mZLZ*FYMify*c_)?jI5;H3=XEegv={2eA@D5exVXvMpXd!U>sN zvrhvRQA%`N!sULQiY)R-mR(R9KqOfmr5W?IGM#gtNEQ~@91vUM$86*`PRF|a0c_JG zh@kerd^JlJMFFLQxAof@XhJ&6rM9VXut}J{*N$S#EmfZQxQQm*8B>dHz!?aOeeg}s z8$E_Fe17tmBDQk8o2x7D^M;$P8gdOBimGPvQzwS*6izM{kHm@RHyeN@SwD4PQYkCf z&q7pPFZ*Hk6#zH@tUk{#7D)d_tGYMozJ^r zxexaOEzevty1(5>U*@^f2#Z4RX&lmo%Li6b3y6dj$U}rI$j}(#$&5hrfhvwV`GXZK z5N91bb2yd(#}K1G*7i}gAu4f9G{?nDdk8zy<9d8Qb!YY-IOx4)Lo>svgemZ-ACRj| zBd<@FYv5JLdkd`<$8RB{#_NO7+iX_zva+UNy~={5A9qB*FT6<$;ESN;r_a`LFoU{W zdp7B*M2F1;tG`PX@=r7LA5sWL1G>{D<{m!yB7uIgW`Y((*ZILNhUKNwSMEowBsLXI znv_iR#ZNxVGx#4lPj2m#Nmh_`Bnd~qrwN=Vrg*OHS{}_p;33|qCVEtnM}``o1gQui zOuSEd)rtj-aW`WPjb^b@JK{{jpBb$bN1}r-UiLcY|AFr3X z4=JmvA7>Pq6VIJ4f|81?n(Z9|yy!54@v73i_-32~WN~fPmXwVKk)xF!nt5~jzaH-G zl?fx2R^~P1oKK#cvcS4g8cFrPCX^7!LYy4QE~#h)wI@UhqUE3*4MF&3qPqPRxVWBW zL3+_)9Ud~=9#A8h_2O5D$}{IfSn-SGBYs$}(fdvT5u<&jF>t^omd|EqClsn+chL>F zE&Mqh2OCq!-4bN8$0|jJ-@+A>=0Y_j%2I zROi!N;fNtHLAO(4xaS$71sF%h+EY*|M=c|d_$DvSa229?)UVPm+VbZJ&=*SfTQ%=s zF#-rf7YpPjdYY=h1qFGjaxMI3?j2S#&yb_jJHoF?N(f=FQRT|h^k6EVFfv~0j=ML* zee)2EkIGQXuj{L$qxm?ah(FHhFJC583lA0Y;g5qt=5NRvDri zEC3)v6c2Z5lZ2ji3MoL(9+|Tn3IM)Rc-&IimI`y*kqDiQySF!A&I~gX6ALl`0>-w< zh^ZM!buFIZfKIGPIanhO@2{VX>k`nRXQYp%HDp7C-#BVCPN&5D(b7(+D#@COa7s%iYay^r(3CxQN~~`SvN@vYM=AWcYV~4odAr$ zMo_5I5dIg?cTRc-1p^w1YTsiC49W?c^JU88hF6WYc@Z z=;~0=64V%~{>Cuy#mlxwsU8OKxR*|I{!x#jDBLJS%Tql`pq4BcQhh?}cx`ji6$i41CR@edvzRSdYf}X5!B*MXTac~)K-|V8>F<}8d$ootkzs~ z6VRC(!TU_}uvV?a-pwRz+T53UeJo>;oAJi>K|_H}yQ{mm_)epHB3@+fZyc&Jbx&C^ zUP@1ZuYB{q@epXM9{W8_)?>!xM^`2)mzb`$w(+*d#_geI2IC2q4KHrBA>A}?Le{^3 zJpu`9-LXy|y5;6_{bjr9PFhn?giFN12{BVF1jtJslQJHTX`MLMsRU8t!mS~USbhcU z1c4{p;;UduOUObrqv>41i8p?aKN&R4E0wZ+LtF;^2ewHz`Q@|aO0u~_)Xw6n_@_k* zz<|d5TEECknSYUsWCEBlWW8BGn=#KmWW|}=Y{Z*PRCxxhUIeOJ@bB3C^?J)$GqDZp zE1t+LHgj>es0oSg*Ev%n{j8#`Z7?$RkIlwjuca`3m zucQY@;PeHsbun6VnGj&wT@urx+L_aOiQpz^;OZ&eXvBCxi7ODP=XG>;E{+0a^4i8m zyLqm5SE?Lb+s_=GK%8CR8@ZiN_l6G%ZpQ^Ot24F6Pj+2oT~{bh_B^==E-o*N=7w-s ziLs<=#*k?YOGS@fP#9~{N({s=dd+IPG2%@Y8MqK41W#OaFhCXo?eR*&zuj!W+q$|a zZ%f+nGjhoSWTg=$5mP2GHgTsu`?*wELIw+8{{Co{2@WxeG>$&jUE~?kGj2eq9ygX6 zS7qUqQc6_(<(TrJ8A;NxC8+=JU?enOJpR9l5X?9P@F)96t;j8kc0P5LIe2HC3C3km z(#y4P(cqt`ZfFl3o#j-_xX*8BNX{G?S!Y&+(Fy0f_RIzEJ~>1AN;hlFv#0IXY2Wlv zelxofq*Nc@dk|5EsWA92qCeUrbL{i%vlv&CXUnSfpLgz`*Ubt$2L*EdmaG9}l;7Gul}3J= z>R%wE!vl~d2<07+-2)`)FQ6#_y5VHC0{NA7QbMp z`Tkd72TIY1FaU6xXYqf$+_YqMO5Uj(0K(AauSS}{a~GMZXJpx%^qSLr@%U|J@I+@_!v`U28RIuUy+iBAm?+gbqpZW zcI-LoyaZ4*i^skX+??<6OWuFuI<`-|oRxWWC%BN31(br?OP&)D*;tHti4rpQ(C>~r zR)zT~+6364)gu8Nw`40^TB?NLcy4@;JUecY^gRDQ&dU0@vchgWaR-nwdKiEDv+TU6 zlxAPaK^58XAxq4?XXxwGY|>~MAtm^H~X>Mv=Zgr#5N%_ zW^fC+#A=)0VDR4oP9NLw+cdykM>XVFyh_OEDoyy!mqYsdHm!m~<`cNW7#j+wp*a*- zudLj_8q19vqBxnrKn(*d8}(tu)^x%<2@jDo2f2AhD*Y-vEjlWKB@rBY48oAqN%G;t zzf#hsFHs}^zEcZT+dg+?GOcI*>4L7dF3Y>KtWNOv545hPL*0G6Pd~ zJZld8B?W`1jsb$1U=EkH%SXQ6G{1j5O}2X__a0LUs%(Yao9e`7A-&Dc49d4Vk#i_r z5hg*I*UkFpmyk!w{)MEf?gq2~tLns4gr$4LgloEFbAmBkT^U4$A=Za?jK9=lK(G#!L(X*GC5rw+vJo{;fbER6VF_9KvrO{l6$WMi`ravF;gH@|(NCQ^#rXyb z+mgtYJwMUVbknxJhOc0&pK~GvN-!I*w@Jge)Lw8)dcs9K7({Q{Lsh(Ze^VmsRG8JUu+PF z##3xp9*g_?MayclPP%>KEE!XS$n`6)I7MvAne5`Wg>%jLyY{hdF3zj{o{gO)E-EDu zay;4O`$&C#18u7$#j`E?ME-VOk!If;;85UmPBDg=<@Q>u%EN~{hAdeEkZq38)M}ly zYIQJz1Nwrl4!nT$&Qeab-5jO&o$>WxZ}@kO1^L#%NDOfSl=3l-r<5~#-&hC}&_Cw2 zLEPnm=s97y9_xl6kJT9ch&25i9rrf}m4mIyBYEZgAj@~8%9CV=K?exdp6Z`usOpA`%;A*2@?s!8G-rW6B{kQ{YRx_WxV$f3=rMh^C&DkWS* zj{{DY3QO4|a=-k_%k3QN_W;2CU)gqRjnJy&bTG%8a->S<>L#!wKtnVneESD>r(L%I zmz;ge-(UP!dJ!skfXyPJs1lH?an!(Hi4Eb5{uHT*VpBT9{*A=3hx}a=i&OpbJG}3N z%!!LM51T$S0Jmzu8+cjIJj;o&H+!UFJ#)hcZeBj3)V)}gBZ~KIE-TK~f$=8vqh)#$ zs+luq_uaoQ%6%_f+3wSjhk?XH4$YVfA=6vp4)MrDs14+CM@l96yjd9YbS*P^vS><| zE7539Lg|AsX8kvy2eoolvw~C4tt=JXG~m^x%@mQZ!IYx^vFGLcz}2xoNJXCej*3w3 zVb-PM@%G==uJHG=QGlF?&Dc6QkxoAQtC=fi{3QqC)*3sd?27kB1=c@m;5dRIH-L~Z z!FoL^{vUh;q?FxGsVMcobC(?ilkvYltJDBq*LEv_bg2c#(4q}3Gn5IVAE39Nq`XJ=z+5w``km#3Yvqtchi=^2@c;_?URlH@ zuX~r9DY~5l!Qs#8H#Gl52C?;Ud3(xmn$6Lbps!6d;Xr~ZhCcm;KjYFd)SJ?$fHBsBI`aupCu2r7^$9?Bq_V|2?Q7=iA=yAfPd(Y$bMImnWPE#!!QL7 z{zljyz}%`&-{_=Hf9z)}ErTVo^?{K$M=PY~Ae7ZE?8X2_Tu5z^mMC`MUMEScT7O~U z`OnprUI=WS?1@CCc!#GU+rqq=XdLecCSviPWZsUU`e>NG^53-fqwF9Qh;zs!>kD_E z6?Htn3dMbk&NSfk)Y2MD;qG{W8dHcdTv+`i4c?`krxTMI*PLBEHjI6I2V`SoW6aa3AQlg6B=JmgLlM8=_%B$WmdPppBIw@_ zF#~J-8uGL~u#QVzwvP%7Q`j0r?|p`%Rs6F)aX5g+tGEO zo*AlTsa_MZ{z{wk852-TON-_@yzeZ@l!W(z83XHx}l1P`6`y%A*s_34nf*s$nIN%d#-Jc-Xsqbyo za>=n(qgGM5tqQw+ae>^{$YpbrXJC9huX;nkIpi~>;G}NtwB^Cw$xassE@kLo6-q28 z^1*M;7ND=i4LQ2=$gcI}kxLWw%piUxi)j?aP%N0mBbFiUT&BFirOO^KuJJmPBd}nn zE59Z3KXX$Oz5Z%B**AJBK~mPr)cPU*-UG??u;_o_t+7$p9E{&O{*PA4ik=Z+@RKY+aQ+m78wT0 zAVymQ1kynsSrWuDBd~#nt!&G){50M6#5h0sXVu3A-SCevK_66a*km|tr8fGSbi5Tx zh+p5|=rlQlws6a4+V40sqA;vj`xDTF6frnCIp2bQ?(ZK)29~NFvg{zbVQEG*wln2t2!7TULMRYa+!XZUyol0_Bbt!rH3#!sTO4b+rl@oyOa%YYN>fKR^ih zvA0YcI|i*Lo0MtS^b3cn4D7_j!frj@F#Z|@h6k));5PG?3qoERWcNM#`rXIZ(NRQ_ z7#r~Fr>%+H932;2Anf2V$HwuLA5%F1YkN(H@3S7pp`NtL4i9K+xr$_as>;qG#1uG!Z zIV|R(zkjUB71Wz#|3fEe3h>b}kkmzZp`0{mJn>pIo1y4OBpM&R zj69ykNYoQs*j8ys%q~3vwZnW5ED^Z_vr-DF0lGW0#)XP3edzG%4E9Jqej=H#r$-fH=Kcf`Mv0%<*>)EE8`@1* z))Jd08+#*dy0EWElBT<2Hzbpe`0A)~MWX;Nfe^KjVX0Q3>IfA``>7g^J)XI7(tp&# z@~97`9{{!V8xg}4g7J@9-M|mx$fP*eOc@%k=xpW}^D=>Rm07zL4ApUyyV6 z;ML=5M->Obg;|#kYJ1=8@*GL}3$e~TdR|>ltJyEnyl(T$y8E9M7ijh~sRT*+|7beP zs4BZ|4byJAr4f|wZs{&*De0CjDd`UB?(S}pl;1+!`;YoV$M#`8Yu0^V zGdYY@L^yH{R@7r2t%WAso9^^-cZ{fZfo(_wn2J=+CEnV#W$(d< z2D<7ZoxmF^h^ZD|@cPEWwgmRp)NUzf==# zQlmM13M?uqH@bc}TqN}RUWxE4K=U4oY(JXt5D$}MWx=9$$d1{RZ~3+w6t_PFSvEE# z@lm9U{cLSLf6G8Ibmt`_<7Fr+&O@MSMgH}Q(*m~n&NrJOT{WID+}{PLcvq!NxYApF zHHvh_m<=Yh1|X(mhQoOnf;6STMtn~@M~*p|euTKiT8p?QFt|s4UHOBysq*){*w5NL z?JeXb)V#3JtoxXI1z=H1)9`T7jnz-3Gwn0R_H~*oDlchW)2F_s3-k^6+*sny?P8EL zLirnXM%XADKECk@ByyW&2+fzV>KusbATIe?tprh|mekRz@RpXZWZbODzPP++uw`^V zy|}nII0&1FOO+`4R}3W-3Cyr`zS|ocpjf$9-Qzs8&*opVZE0!g`aXGj3w&jODM6Oq zW)c?4dImX!W}Mt=&4u&z`kv?1AfI zT#foS;J2l{`Zf{WW_b=H4sXl$roa=?A}moHKM8@O3n%E79$T6`7ornbBOI3SXU` zoz40Re=JM8B^EN2)_<)(X$S1&ek!)OR>e}{b*b)Ip(?_zU;c@7s1u72sOsY?vDJJc zwffNyglfIC1#itnBk1RP(#vu!w2pq7CE}KrZt9Ykj%{44!8*7`*?9hi)FU#@nMLs` z_Q=fTl$Fg{CC&w(W}Q*;>}E)h~LB5+>w+ZrLlbw`kKZ z8fY`W8n3?nqHY{*-%+d79HBbdu1YZonW_LUEFb>6@3Q4;UXtltDQIyvEG3sOFQm(U z1^bgVb0ORGVKR8ad{F=ikU&*o3Z^eB)5o^W`O)m3uzAZqI8oI|Gb`D^GuL6n$>)Ze zA*u$<(!Hlp*0ROiV2RM2k=C2iTsXQ02_#O*u0n(25~Sd_tccsZ&8XX7%@X~ipGAk3 zEH-k^>7@u$_%QxIq=bRXIep}Qf`W(Y?M_}#}7Rc5~%8qhg)?q99w<0^IP zP6$Vjj_fMp12{h5+~?)9a=I@Ck)iX8WSraaiSOWVIkt{Iz1HE(%ACwYI`p{}45HAnw4_gX+-pp!#yf;mf5S>W4c3@0HM$6Bcpx{B)%-_-0Tn8rN}n zD?Dse5!#sTyWD$W^C<$-taym*NZMCz)Jf;eMPEGYdp7&IMRjuk>LH1b&YSUnAJ!b_9rT zaUer!UDY&EWWxmI5xo*@wD3;FrWLAk>+<@$w>^OqZ1Bv3zR8A>tOkGIHWlu98mOd> zu8_P5qZ+?7oZ%A?7}MKT!98>g`o%CZEQ@RP$t>{dI|81}r}nL22k*#IVT98!l^R|e zy&PL1(kU=>ERlNMiUg|hgoT!Tnc_~wWYCF8fx#8C-B}&%bxP}oFah1d+P?ZT{SdU$ zGaE5#7vkpN>W~mA>Wqct3;5<9u{_MYb&(Gk{Xa=b-T%0G~|7SaoA+#%$n&BAnp#v z`YqZfjyR-(tL>~(FqmL)gX0=Ia+$}ysO3ZYVX`73FQ#l{L6|?AcYFS*(aX;(-vA?} z2<^UTBuu9L`y-c=n96uVP2>LgI{gNY9R3)i-5SY0-u4yK(GHCi3TC{~kMsEtF_GS>Q^k6SN_%&B%o5p@{j!t7c8dpqDt zgj?-15@G2K3^ic<#D|rK^MbE(CL#SJ9aJUBrZRz)ij;Z-QK8a~uJGzsMf zcu1oRoJT0XWm}ZhKaw*Fj6R0s(wuZ8K6fbu?&@C2gf`>fg)itMsa9I$i@^Ua_{7}&Y#T=Bt^xp8AUlxNdn;tUkVP{)VK zN87F}SaVfD+-6gyXE_(+N0+W&Eq3kOwLkacc+-(L2>#e*Qk59u%6nFv6W(21^L|vw zryaqfMVrRa1C=Fr3CMEPwV)xt=trjS<#_raSwO?V2wdj4L-aaTSg6Y_Dd=?yoJx8a zE_GS(NQ$k$_O4DCBXQkx;osgA%^Uz1{=Y7H@J;ZRFM?UgPY8DIaf+?!C)oV+XlDJt zuo~C^Q!FrFj|HV$ijG~|7{fhzxVRL?3$w$MYx7g`SZ23b5a@+rHbl*=ti})Z+GvJa zN+<*9PG14C#qEU%f?5B~+1dJRM8O9XC0U(Fmu!#GP~#q9J6qfJR)IG!p%6)DhQD+i zd5Oe;v*Eo9h*0J)(}4SwyBmaIYiAd+rA?nIjxjcO{&?ES{{_G{Bq;v%sK+}$(SVo< z7%MBke+SU`_QeD==l}K~`oN=^oXf1=0qj&wJQ_{uXa4~PJQ(KQP-2zS{oo2N3d7W- zgl#!~e!N|}X<7So)Tq`CL|C9L@k2{_!cE|aTsk zG>Ije3b&Drx2Y3U*hs-F!Htv;W>w!BOd8|Xi^se(zCK%m!4Qs8NnFl}!t9llV1Lgs z>}mj&h0W;UG9k@#;|5RvF|upN?JcR%hg6Ofo*ZG4yvqDk#+~yU&N;EPeo=jWnLC9g zo&vg`&W0H%M~eMnFmq-P5yx(RI|Yo>C!$nv{6Dcn+>n@@B+M z-XBLXw}wxWd`K8d7}A+P+gMn>@7}v00|9e?04rAekydd#hA4uxJhami3+z_}f*