commit 242fb71511f3e4ad9fe29f1dd75a00ef385eedc6 Author: jiegeaiai Date: Mon Sep 9 08:17:43 2024 +0800 添加热更 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d8ebcca --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Binaries +Intermediate \ No newline at end of file diff --git a/HotPatcher/HotPatcher.uplugin b/HotPatcher/HotPatcher.uplugin new file mode 100644 index 0000000..16f781e --- /dev/null +++ b/HotPatcher/HotPatcher.uplugin @@ -0,0 +1,44 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "HotPatcher", + "Description": "", + "Category": "HotPatcher", + "CreatedBy": "imzlp", + "CreatedByURL": "https://imzlp.com/", + "DocsURL": "https://imzlp.com/posts/17590/", + "MarketplaceURL": "", + "SupportURL": "", + "CanContainContent": true, + "IsBetaVersion": false, + "IsExperimentalVersion": false, + "Installed": false, + "Modules": [ + { + "Name": "HotPatcherEditor", + "Type": "Editor", + "LoadingPhase": "Default" + }, + { + "Name": "HotPatcherCore", + "Type": "Editor", + "LoadingPhase": "PreDefault" + }, + { + "Name": "HotPatcherRuntime", + "Type": "Runtime", + "LoadingPhase": "PreDefault" + }, + { + "Name": "BinariesPatchFeature", + "Type": "Runtime", + "LoadingPhase": "PreDefault" + }, + { + "Name": "CmdHandler", + "Type": "Developer", + "LoadingPhase": "PostConfigInit" + } + ] +} \ No newline at end of file diff --git a/HotPatcher/Resources/ButtonIcon_40x.png b/HotPatcher/Resources/ButtonIcon_40x.png new file mode 100644 index 0000000..7acb8e8 Binary files /dev/null and b/HotPatcher/Resources/ButtonIcon_40x.png differ diff --git a/HotPatcher/Resources/Icon128.png b/HotPatcher/Resources/Icon128.png new file mode 100644 index 0000000..987abd1 Binary files /dev/null and b/HotPatcher/Resources/Icon128.png differ diff --git a/HotPatcher/Resources/Payments/alipay.png b/HotPatcher/Resources/Payments/alipay.png new file mode 100644 index 0000000..a2c5d2e Binary files /dev/null and b/HotPatcher/Resources/Payments/alipay.png differ diff --git a/HotPatcher/Resources/Payments/wechatpay.png b/HotPatcher/Resources/Payments/wechatpay.png new file mode 100644 index 0000000..faa2a90 Binary files /dev/null and b/HotPatcher/Resources/Payments/wechatpay.png differ diff --git a/HotPatcher/Source/BinariesPatchFeature/BinariesPatchFeature.Build.cs b/HotPatcher/Source/BinariesPatchFeature/BinariesPatchFeature.Build.cs new file mode 100644 index 0000000..4113362 --- /dev/null +++ b/HotPatcher/Source/BinariesPatchFeature/BinariesPatchFeature.Build.cs @@ -0,0 +1,37 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using System; +using System.IO; +using UnrealBuildTool; + +public class BinariesPatchFeature : ModuleRules +{ + public BinariesPatchFeature(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + Path.Combine(EngineDirectory,"Source/Runtime/Launch"), + Path.Combine(ModuleDirectory,"Public"), + Path.Combine(ModuleDirectory,"../HotPatcherRuntime/Public/Templates") + // ... add public include paths required here ... + } + ); + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "Projects", + "Json", + "JsonUtilities", + // ... add other public dependencies that you statically link with here ... + } + ); + bLegacyPublicIncludePaths = false; + OptimizeCode = CodeOptimization.InShippingBuildsOnly; + } +} diff --git a/HotPatcher/Source/BinariesPatchFeature/Private/BinariesPatchFeature.cpp b/HotPatcher/Source/BinariesPatchFeature/Private/BinariesPatchFeature.cpp new file mode 100644 index 0000000..af8a4cf --- /dev/null +++ b/HotPatcher/Source/BinariesPatchFeature/Private/BinariesPatchFeature.cpp @@ -0,0 +1,42 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "BinariesPatchFeature.h" +#include "HotPatcherTemplateHelper.hpp" + +#include "Resources/Version.h" +#include "Features/IModularFeatures.h" +#include "Misc/EnumRange.h" +#include "Modules/ModuleManager.h" +#include "UObject/Class.h" + +DECAL_GETCPPTYPENAME_SPECIAL(EBinariesPatchFeature) + +void OnBinariesModularFeatureRegistered(const FName& Type, IModularFeature* ModularFeature) +{ + if(!Type.ToString().Equals(BINARIES_DIFF_PATCH_FEATURE_NAME,ESearchCase::IgnoreCase)) + return; + IBinariesDiffPatchFeature* Feature = static_cast(ModularFeature); + THotPatcherTemplateHelper::AppendEnumeraters(TArray{Feature->GetFeatureName()}); +} +void OnBinariesModularFeatureUnRegistered(const FName& Type, IModularFeature* ModularFeature) +{ + +} + +void FBinariesPatchFeatureModule::StartupModule() +{ + TArray RegistedFeatures = IModularFeatures::Get().GetModularFeatureImplementations(BINARIES_DIFF_PATCH_FEATURE_NAME); + for(const auto& Featue:RegistedFeatures) + { + THotPatcherTemplateHelper::AppendEnumeraters(TArray{Featue->GetFeatureName()}); + } + IModularFeatures::Get().OnModularFeatureRegistered().AddStatic(&OnBinariesModularFeatureRegistered); + IModularFeatures::Get().OnModularFeatureUnregistered().AddStatic(&OnBinariesModularFeatureUnRegistered); +} + +void FBinariesPatchFeatureModule::ShutdownModule() +{ + +} + +IMPLEMENT_MODULE( FBinariesPatchFeatureModule, BinariesPatchFeature ); diff --git a/HotPatcher/Source/BinariesPatchFeature/Public/BinariesPatchFeature.h b/HotPatcher/Source/BinariesPatchFeature/Public/BinariesPatchFeature.h new file mode 100644 index 0000000..1bc847e --- /dev/null +++ b/HotPatcher/Source/BinariesPatchFeature/Public/BinariesPatchFeature.h @@ -0,0 +1,33 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "Features/IModularFeature.h" +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" +#include "Misc/EnumRange.h" +#include "BinariesPatchFeature.generated.h" + +#define BINARIES_DIFF_PATCH_FEATURE_NAME TEXT("BinariesDiffPatchFeatures") + +UENUM(BlueprintType) +enum class EBinariesPatchFeature:uint8 +{ + None, + Count UMETA(Hidden) +}; +ENUM_RANGE_BY_COUNT(EBinariesPatchFeature, EBinariesPatchFeature::Count); + +struct IBinariesDiffPatchFeature: public IModularFeature +{ + virtual ~IBinariesDiffPatchFeature(){}; + virtual bool CreateDiff(const TArray& NewData, const TArray& OldData, TArray& OutPatch) = 0; + virtual bool PatchDiff(const TArray& OldData, const TArray& PatchData, TArray& OutNewData) = 0; + virtual FString GetFeatureName()const = 0; +}; + +class FBinariesPatchFeatureModule : public IModuleInterface +{ +public: + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; \ No newline at end of file diff --git a/HotPatcher/Source/CmdHandler/CmdHandler.Build.cs b/HotPatcher/Source/CmdHandler/CmdHandler.Build.cs new file mode 100644 index 0000000..c1a4b12 --- /dev/null +++ b/HotPatcher/Source/CmdHandler/CmdHandler.Build.cs @@ -0,0 +1,51 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System; +using System.Collections.Generic; +using System.IO; + +public class CmdHandler : ModuleRules +{ + public CmdHandler(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + bLegacyPublicIncludePaths = false; + OptimizeCode = CodeOptimization.InShippingBuildsOnly; + + 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", + "SandboxFile", + "JsonUtilities", + // ... add other public dependencies that you statically link with here ... + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "InputCore", + "CoreUObject", + "Engine", + // ... add private dependencies that you statically link with here ... + } + ); + } +} diff --git a/HotPatcher/Source/CmdHandler/Private/CmdHandler.cpp b/HotPatcher/Source/CmdHandler/Private/CmdHandler.cpp new file mode 100644 index 0000000..7721522 --- /dev/null +++ b/HotPatcher/Source/CmdHandler/Private/CmdHandler.cpp @@ -0,0 +1,149 @@ +// Copyright 2019 Lipeng Zha, Inc. All Rights Reserved. + +#include "CmdHandler.h" + +#include "Misc/CommandLine.h" +#include "Misc/ConfigCacheIni.h" + +DEFINE_LOG_CATEGORY(LogCmdHandler); + +bool OverrideConfigValue(const FString& FileName,const FString& Section,const FString& Key,int32 NewValue) +{ + bool bRet = false; + int32 DefaultValue; + if(GConfig->GetInt( *Section, *Key, DefaultValue, FileName )) + { + GConfig->SetInt( *Section, *Key, NewValue, FileName ); + UE_LOG(LogCmdHandler, Display, TEXT("Override Section: %s key: %s from %d to %d."), *Section,*Key,DefaultValue,NewValue); + bRet = true; + } + else + { + UE_LOG(LogCmdHandler, Warning, TEXT("Override Section: %s key: %s is not found!"), *Section,*Key); + } + return bRet; +} + +#if WITH_EDITOR +static bool bDDCUrl = false; +static FString MultiCookerDDCBackendName = TEXT("MultiCookerDDC"); +bool AddMultiCookerBackendToConfig(const FString& DDCAddr) +{ + bool bStatus = false; + UE_LOG(LogCmdHandler,Display,TEXT("-ddcurl: %s"),*DDCAddr); + if(DDCAddr.IsEmpty() || DDCAddr.StartsWith(TEXT(" "))) + { + UE_LOG(LogCmdHandler, Warning, TEXT("not use MultiCookerDDC")); + return bStatus; + } + auto EngineIniIns = GConfig->FindConfigFile(GEngineIni); + auto MultiCookerDDCBackendSection = EngineIniIns->FindOrAddSection(MultiCookerDDCBackendName); + MultiCookerDDCBackendSection->Empty(); + + auto UpdateKeyLambda = [](FConfigSection* Section,const FString& Key,const FString& Value) + { + if(Section->Find(*Key)) + { + Section->Remove(*Key); + } + UE_LOG(LogCmdHandler, Display, TEXT("Override Section MultiCookerDDC key: %s to %s."),*Key,*Value); + Section->Add(*Key,FConfigValue(*Value)); + }; + + UpdateKeyLambda(MultiCookerDDCBackendSection,TEXT("MinimumDaysToKeepFile"),TEXT("7")); + UpdateKeyLambda(MultiCookerDDCBackendSection,TEXT("Root"),TEXT("(Type=KeyLength, Length=120, Inner=AsyncPut)")); + UpdateKeyLambda(MultiCookerDDCBackendSection,TEXT("AsyncPut"),TEXT("(Type=AsyncPut, Inner=Hierarchy)")); + UpdateKeyLambda(MultiCookerDDCBackendSection,TEXT("Hierarchy"),TEXT("(Type=Hierarchical, Inner=Boot, Inner=Pak, Inner=EnginePak, Inner=Local, Inner=Shared)")); + UpdateKeyLambda(MultiCookerDDCBackendSection,TEXT("Boot"),TEXT("(Type=Boot, Filename=\"%GAMEDIR%DerivedDataCache/Boot.ddc\", MaxCacheSize=512)")); + + FString DDC = FString::Printf( + TEXT("(Type=FileSystem, ReadOnly=false, Clean=false, Flush=false, DeleteUnused=false, UnusedFileAge=10, FoldersToClean=10, MaxFileChecksPerSec=1, Path=%s, EnvPathOverride=UE-SharedDataCachePath, EditorOverrideSetting=SharedDerivedDataCache)"), + *DDCAddr + ); + UpdateKeyLambda(MultiCookerDDCBackendSection,TEXT("Shared"),DDC); + + FString DDCBackendName; + if(!FParse::Value(FCommandLine::Get(),TEXT("-ddc="),DDCBackendName)) + { + DDCBackendName = MultiCookerDDCBackendName; + FCommandLine::Append(*FString::Printf(TEXT(" -ddc=%s"),*DDCBackendName)); + UE_LOG(LogCmdHandler, Display, TEXT("Append cmd: %s"),FCommandLine::Get()); + bStatus = true; + } + + UE_LOG(LogCmdHandler, Display, TEXT("Use DDCBackend: %s"),*DDCBackendName); + return bStatus; +} + +void OverrideEditorEnv() +{ + int32 OverrideNumUnusedShaderCompilingThreads = 3; + if(FParse::Value(FCommandLine::Get(), TEXT("-MaxShaderWorker="), OverrideNumUnusedShaderCompilingThreads)) + { + OverrideNumUnusedShaderCompilingThreads = FMath::Max(OverrideNumUnusedShaderCompilingThreads,3); + const int32 NumVirtualCores = FPlatformMisc::NumberOfCoresIncludingHyperthreads(); + OverrideNumUnusedShaderCompilingThreads = NumVirtualCores - OverrideNumUnusedShaderCompilingThreads; + } + GConfig->SetInt( TEXT("DevOptions.Shaders"), TEXT("NumUnusedShaderCompilingThreads"), OverrideNumUnusedShaderCompilingThreads, GEngineIni); + OverrideConfigValue(GEngineIni,TEXT("DevOptions.Shaders"),TEXT("NumUnusedShaderCompilingThreads"), OverrideNumUnusedShaderCompilingThreads); + + int32 OverrideMaxShaderJobBatchSize = 10; + FParse::Value(FCommandLine::Get(), TEXT("-MaxShaderJobBatchSize="), OverrideMaxShaderJobBatchSize); + OverrideConfigValue( GEngineIni,TEXT("DevOptions.Shaders"), TEXT("MaxShaderJobBatchSize"), OverrideMaxShaderJobBatchSize); + + int32 OverrideWorkerProcessPriority; + if(FParse::Value(FCommandLine::Get(), TEXT("-SCWPriority="), OverrideWorkerProcessPriority)) + { + OverrideConfigValue( GEngineIni,TEXT("DevOptions.Shaders"), TEXT("WorkerProcessPriority"), OverrideWorkerProcessPriority); + } + + FString DDCURL; + if(FParse::Value(FCommandLine::Get(),TEXT("-ddcurl="),DDCURL) && !DDCURL.IsEmpty()) + { + bDDCUrl = AddMultiCookerBackendToConfig(DDCURL); + } +} + +void UnOverrideEditorEnv() +{ + if(bDDCUrl) + { + auto EngineIniIns = GConfig->FindConfigFile(GEngineIni); + EngineIniIns->Remove(MultiCookerDDCBackendName); + } +} + +#else + +void OverrideRuntimeEnv() +{ + +} + +void UnOverrideRuntimeEnv() +{ + +} +#endif + +void FCmdHandlerModule::StartupModule() +{ +#if WITH_EDITOR + OverrideEditorEnv(); +#else + OverrideRuntimeEnv(); +#endif +} + +void FCmdHandlerModule::ShutdownModule() +{ +#if WITH_EDITOR + UnOverrideEditorEnv(); +#else + UnOverrideRuntimeEnv(); +#endif +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FCmdHandlerModule, CmdHandler) \ No newline at end of file diff --git a/HotPatcher/Source/CmdHandler/Public/CmdHandler.h b/HotPatcher/Source/CmdHandler/Public/CmdHandler.h new file mode 100644 index 0000000..abb7345 --- /dev/null +++ b/HotPatcher/Source/CmdHandler/Public/CmdHandler.h @@ -0,0 +1,17 @@ +// Copyright 2019 Lipeng Zha, Inc. All Rights Reserved. + +#pragma once + +// engine header +#include "CoreMinimal.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogCmdHandler,All,All); + +class FCmdHandlerModule : public IModuleInterface +{ +public: + static FCmdHandlerModule& Get(); + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/CommandletHelper.cpp b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/CommandletHelper.cpp new file mode 100644 index 0000000..c08eeef --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/CommandletHelper.cpp @@ -0,0 +1,198 @@ +#pragma once +#include "CommandletHelper.h" + +#include "ETargetPlatform.h" +#include "FlibPatchParserHelper.h" +#include "ThreadUtils/FProcWorkerThread.hpp" +#include "HotPatcherLog.h" +#include "Engine/Engine.h" + +void CommandletHelper::ReceiveMsg(const FString& InMsgType,const FString& InMsg) +{ + UE_LOG(LogHotPatcherCommandlet,Display,TEXT("%s:%s"),*InMsgType,*InMsg); +} + +void CommandletHelper::ReceiveShowMsg(const FString& InMsg) +{ + UE_LOG(LogHotPatcherCommandlet,Display,TEXT("%s"),*InMsg); +} + +TArray CommandletHelper::ParserPatchConfigByCommandline(const FString& Commandline,const FString& Token) +{ + TArray result; + TMap KeyValues = THotPatcherTemplateHelper::GetCommandLineParamsMap(Commandline); + if(KeyValues.Find(Token)) + { + FString AddPakListInfo = *KeyValues.Find(Token); + AddPakListInfo.ParseIntoArray(result,TEXT(",")); + } + return result; +} + +TArray CommandletHelper::ParserPlatforms(const FString& Commandline, const FString& Token) +{ + TArray result; + for(auto& PlatformName:ParserPatchConfigByCommandline(Commandline,Token)) + { + ETargetPlatform Platform = ETargetPlatform::None; + THotPatcherTemplateHelper::GetEnumValueByName(PlatformName,Platform); + if(Platform != ETargetPlatform::None) + { + result.AddUnique(Platform); + } + } + return result; +} + +TArray CommandletHelper::ParserPatchFilters(const FString& Commandline,const FString& FilterName) +{ + TArray Result; + for(auto& FilterPath:ParserPatchConfigByCommandline(Commandline,FString::Printf(TEXT("Add%s"),*FilterName))) + { + FDirectoryPath Path; + Path.Path = FilterPath; + Result.Add(Path); + } + return Result; +} + +static bool IsRequestingExit() +{ +#if ENGINE_MAJOR_VERSION > 4 || (ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION > 23) + return IsEngineExitRequested(); +#else + return GIsRequestingExit; +#endif +} + +static void RequestEngineExit() +{ +#if ENGINE_MAJOR_VERSION > 4 || (ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION > 23) + RequestEngineExit(TEXT("GeneralCommandlet ComWrapperShutdownEvent")); +#else + GIsRequestingExit = true; +#endif +} + +void CommandletHelper::MainTick(TFunction IsRequestExit) +{ + GIsRunning = true; + + //@todo abstract properly or delete + #if PLATFORM_WINDOWS// Windows only + // Used by the .com wrapper to notify that the Ctrl-C handler was triggered. + // This shared event is checked each tick so that the log file can be cleanly flushed. + FEvent* ComWrapperShutdownEvent = FPlatformProcess::GetSynchEventFromPool(true); +#endif + + // main loop + FDateTime LastConnectionTime = FDateTime::UtcNow(); + + while (GIsRunning && + // !IsRequestingExit() && + !IsRequestExit()) + { + GEngine->UpdateTimeAndHandleMaxTickRate(); + GEngine->Tick(FApp::GetDeltaTime(), false); + + // update task graph + FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread); + + // execute deferred commands + for (int32 DeferredCommandsIndex=0; DeferredCommandsIndexDeferredCommands.Num(); DeferredCommandsIndex++) + { + GEngine->Exec( GWorld, *GEngine->DeferredCommands[DeferredCommandsIndex], *GLog); + } + + GEngine->DeferredCommands.Empty(); + + // flush log + GLog->FlushThreadedLogs(); + +#if PLATFORM_WINDOWS + if (ComWrapperShutdownEvent->Wait(0)) + { + RequestEngineExit(); + } +#endif + } + + //@todo abstract properly or delete + #if PLATFORM_WINDOWS + FPlatformProcess::ReturnSynchEventToPool(ComWrapperShutdownEvent); + ComWrapperShutdownEvent = nullptr; +#endif + + GIsRunning = false; +} + +bool CommandletHelper::GetCommandletArg(const FString& Token,FString& OutValue) +{ + OutValue.Empty(); + FString Value; + bool bHasToken = FParse::Value(FCommandLine::Get(), *Token, Value); + if(bHasToken && !Value.IsEmpty()) + { + OutValue = Value; + } + return bHasToken && !OutValue.IsEmpty(); +} + +bool CommandletHelper::IsCookCommandlet() +{ + bool bIsCookCommandlet = false; + + if(::IsRunningCommandlet()) + { + FString CommandletName; + bool bIsCommandlet = CommandletHelper::GetCommandletArg(TEXT("-run="),CommandletName); //FParse::Value(FCommandLine::Get(), TEXT("-run="), CommandletName); + + if(bIsCommandlet && !CommandletName.IsEmpty()) + { + bIsCookCommandlet = CommandletName.Equals(TEXT("cook"),ESearchCase::IgnoreCase); + } + } + return bIsCookCommandlet; +} + +TArray CommandletHelper::GetCookCommandletTargetPlatforms() +{ + TArray TargetPlatforms; + { + FString PlatformName; + if(CommandletHelper::GetCommandletArg(TEXT("-TargetPlatform="),PlatformName)) + { + ETargetPlatform TargetPlatform; + THotPatcherTemplateHelper::GetEnumValueByName(PlatformName,TargetPlatform); + TargetPlatforms.AddUnique(TargetPlatform); + } + } + return TargetPlatforms; +} + +TArray CommandletHelper::GetCookCommandletTargetPlatformName() +{ + TArray result; + TArray Platforms = CommandletHelper::GetCookCommandletTargetPlatforms(); + + for(const auto& Platform:Platforms) + { + result.AddUnique(THotPatcherTemplateHelper::GetEnumNameByValue(Platform)); + } + + return result; +} + + +void CommandletHelper::ModifyTargetPlatforms(const FString& InParams,const FString& InToken,TArray& OutTargetPlatforms,bool Replace) +{ + TArray TargetPlatforms = CommandletHelper::ParserPlatforms(InParams,InToken); + if(TargetPlatforms.Num()) + { + if(Replace){ + OutTargetPlatforms = TargetPlatforms; + }else{ + for(ETargetPlatform Platform:TargetPlatforms){ OutTargetPlatforms.AddUnique(Platform); } + } + } +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/Cooker/HotAssetScannerCommandlet.cpp b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/Cooker/HotAssetScannerCommandlet.cpp new file mode 100644 index 0000000..93f3ad3 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/Cooker/HotAssetScannerCommandlet.cpp @@ -0,0 +1,82 @@ +#include "HotAssetScannerCommandlet.h" +#include "CommandletHelper.h" +// engine header +#include "CoreMinimal.h" +#include "FlibPatchParserHelper.h" +#include "Async/ParallelFor.h" +#include "Misc/FileHelper.h" +#include "Misc/CommandLine.h" +#include "Misc/Paths.h" +#include "BaseTypes/FPackageTracker.h" +DEFINE_LOG_CATEGORY(LogHotAssetScannerCommandlet); + + +int32 UHotAssetScannerCommandlet::Main(const FString& Params) +{ + SCOPED_NAMED_EVENT_TEXT("UHotAssetScannerCommandlet::Main",FColor::Red); + Super::Main(Params); + + UE_LOG(LogHotAssetScannerCommandlet, Display, TEXT("UHotAssetScannerCommandlet::Main")); + + FString config_path; + bool bStatus = FParse::Value(*Params, *FString(PATCHER_CONFIG_PARAM_NAME).ToLower(), config_path); + if (!bStatus) + { + UE_LOG(LogHotAssetScannerCommandlet, Warning, TEXT("not -config=xxxx.json params.")); + return -1; + } + + if (bStatus && !FPaths::FileExists(config_path)) + { + UE_LOG(LogHotAssetScannerCommandlet, 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 AssetScanConfig = MakeShareable(new FAssetScanConfig); + + FString JsonContent; + if (FPaths::FileExists(config_path) && FFileHelper::LoadFileToString(JsonContent, *config_path)) + { + THotPatcherTemplateHelper::TDeserializeJsonStringAsStruct(JsonContent,*AssetScanConfig); + } + + TMap KeyValues = THotPatcherTemplateHelper::GetCommandLineParamsMap(Params); + THotPatcherTemplateHelper::ReplaceProperty(*AssetScanConfig, KeyValues); + + FString FinalConfig; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(*AssetScanConfig,FinalConfig); + UE_LOG(LogHotAssetScannerCommandlet, Display, TEXT("%s"), *FinalConfig); + + FHotPatcherVersion CurrentVersion; + { + CurrentVersion.VersionId = TEXT("HotAssetScanner"); + CurrentVersion.Date = FDateTime::UtcNow().ToString(); + CurrentVersion.BaseVersionId = TEXT(""); + UFlibPatchParserHelper::RunAssetScanner(*AssetScanConfig,CurrentVersion); + } + + FString SearchResult; + FString SaveSearchResultPath = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectSavedDir(),FString::Printf(TEXT("HotPatcher/MultiCooker/%s/SearchCookerAssets.json"),FApp::GetProjectName()))); + if(THotPatcherTemplateHelper::TSerializeStructAsJsonString(CurrentVersion,SearchResult)) + { + FFileHelper::SaveStringToFile(SearchResult,*SaveSearchResultPath); + } + + UE_LOG(LogHotAssetScannerCommandlet,Display,TEXT("HotAssetScanner Misstion is Finished!")); + + if(FParse::Param(FCommandLine::Get(), TEXT("wait"))) + { + system("pause"); + } + + return 0; +} diff --git a/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/Cooker/HotAssetScannerCommandlet.h b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/Cooker/HotAssetScannerCommandlet.h new file mode 100644 index 0000000..7e0a376 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/Cooker/HotAssetScannerCommandlet.h @@ -0,0 +1,17 @@ +#pragma once + +#include "HotPatcherCommandletBase.h" +#include "Commandlets/Commandlet.h" +#include "HotAssetScannerCommandlet.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogHotAssetScannerCommandlet, All, All); + +UCLASS() +class UHotAssetScannerCommandlet :public UHotPatcherCommandletBase +{ + GENERATED_BODY() + +public: + virtual int32 Main(const FString& Params)override; + virtual FString GetCmdletName()const override{ return TEXT("AssetScannerCmdlet"); } +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/Cooker/HotCookerCommandlet.cpp b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/Cooker/HotCookerCommandlet.cpp new file mode 100644 index 0000000..257b0c2 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/Cooker/HotCookerCommandlet.cpp @@ -0,0 +1,72 @@ +#include "HotCookerCommandlet.h" +#include "ThreadUtils/FProcWorkerThread.hpp" +#include "FCookerConfig.h" +#include "FlibPatchParserHelper.h" +#include "CommandletBase/CommandletHelper.h" + +// / engine header +#include "CoreMinimal.h" +#include "HotPatcherCore.h" +#include "Misc/FileHelper.h" +#include "Misc/CommandLine.h" +#include "Kismet/KismetSystemLibrary.h" +#include "Misc/Paths.h" + +#define COOKER_CONFIG_PARAM_NAME TEXT("-config=") + +DEFINE_LOG_CATEGORY(LogHotCookerCommandlet); + +TSharedPtr CookerProc; + +int32 UHotCookerCommandlet::Main(const FString& Params) +{ + Super::Main(Params); + UE_LOG(LogHotCookerCommandlet, Display, TEXT("UHotCookerCommandlet::Main")); + FString config_path; + bool bStatus = FParse::Value(*Params, *FString(COOKER_CONFIG_PARAM_NAME).ToLower(), config_path); + if (!bStatus) + { + UE_LOG(LogHotCookerCommandlet, Error, TEXT("not -config=xxxx.json params.")); + return -1; + } + + if (!FPaths::FileExists(config_path)) + { + UE_LOG(LogHotCookerCommandlet, Error, TEXT("cofnig file %s not exists."), *config_path); + return -1; + } + + FString JsonContent; + if (FFileHelper::LoadFileToString(JsonContent, *config_path)) + { + UE_LOG(LogHotCookerCommandlet, Display, TEXT("%s"), *JsonContent); + FCookerConfig CookConfig; + THotPatcherTemplateHelper::TDeserializeJsonStringAsStruct(JsonContent,CookConfig); + + TMap KeyValues = THotPatcherTemplateHelper::GetCommandLineParamsMap(Params); + THotPatcherTemplateHelper::ReplaceProperty(CookConfig, KeyValues); + + if (CookConfig.bCookAllMap) + { + CookConfig.CookMaps = UFlibPatchParserHelper::GetAvailableMaps(UKismetSystemLibrary::GetProjectDirectory(), ENABLE_COOK_ENGINE_MAP, ENABLE_COOK_PLUGIN_MAP, true); + } + FString CookCommand; + UFlibPatchParserHelper::GetCookProcCommandParams(CookConfig, CookCommand); + + UE_LOG(LogHotCookerCommandlet, Display, TEXT("CookCommand:%s %s"), *CookConfig.EngineBin,*CookCommand); + + if (FPaths::FileExists(CookConfig.EngineBin) && FPaths::FileExists(CookConfig.ProjectPath)) + { + CookerProc = MakeShareable(new FProcWorkerThread(TEXT("CookThread"), CookConfig.EngineBin, CookCommand)); + CookerProc->ProcOutputMsgDelegate.BindStatic(&::ReceiveOutputMsg); + + CookerProc->Execute(); + CookerProc->Join(); + } + } + if(FParse::Param(FCommandLine::Get(), TEXT("wait"))) + { + system("pause"); + } + return 0; +} diff --git a/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/Cooker/HotCookerCommandlet.h b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/Cooker/HotCookerCommandlet.h new file mode 100644 index 0000000..9edec55 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/Cooker/HotCookerCommandlet.h @@ -0,0 +1,18 @@ +#pragma once + +#include "Commandlets/Commandlet.h" +#include "CommandletBase/HotPatcherCommandletBase.h" +#include "HotCookerCommandlet.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogHotCookerCommandlet, Log, All); + +UCLASS() +class UHotCookerCommandlet :public UHotPatcherCommandletBase +{ + GENERATED_BODY() + +public: + + virtual int32 Main(const FString& Params)override; + virtual FString GetCmdletName()const override{ return TEXT("OriginalCookerCmdlet"); } +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/Cooker/HotGlobalShaderCommandlet.cpp b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/Cooker/HotGlobalShaderCommandlet.cpp new file mode 100644 index 0000000..de7abdd --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/Cooker/HotGlobalShaderCommandlet.cpp @@ -0,0 +1,88 @@ +#include "HotGlobalShaderCommandlet.h" +#include "CommandletHelper.h" +// engine header +#include "CoreMinimal.h" +#include "FlibHotPatcherCoreHelper.h" +#include "Async/ParallelFor.h" +#include "Cooker/MultiCooker/FlibHotCookerHelper.h" +#include "Kismet/KismetStringLibrary.h" +#include "Misc/FileHelper.h" +#include "Misc/CommandLine.h" +#include "Misc/Paths.h" +#include "ShaderLibUtils/FlibShaderCodeLibraryHelper.h" + +#define PLATFORMS_PARAM_NAME TEXT("-platforms=") +#define SAVETO_PARAM_NAME TEXT("-saveto=") + +DEFINE_LOG_CATEGORY(LogHotGlobalShaderCommandlet); + +int32 UHotGlobalShaderCommandlet::Main(const FString& Params) +{ + SCOPED_NAMED_EVENT_TEXT("UHotGlobalShaderCommandlet::Main",FColor::Red); + Super::Main(Params); + + UE_LOG(LogHotGlobalShaderCommandlet, Display, TEXT("UHotGlobalShaderCommandlet::Main")); + + FString PlatformNameStr; + bool bStatus = FParse::Value(*Params, *FString(PLATFORMS_PARAM_NAME).ToLower(), PlatformNameStr); + if (!bStatus) + { + UE_LOG(LogHotGlobalShaderCommandlet, Warning, TEXT("not -platforms=Android_ASTC+IOS params.")); + return -1; + } + + FString SaveShaderToDir; + if (!FParse::Value(*Params, *FString(SAVETO_PARAM_NAME).ToLower(), SaveShaderToDir)) + { + SaveShaderToDir = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectSavedDir(),TEXT("Cooked"))); + } + + TArray PlatformNames = UKismetStringLibrary::ParseIntoArray(PlatformNameStr,TEXT("+"),false); + TArray Platforms; + for(const auto& PlatformName:PlatformNames) + { + ETargetPlatform Platform; + if(THotPatcherTemplateHelper::GetEnumValueByName(PlatformName,Platform)) + { + Platforms.AddUnique(Platform); + } + } + + UE_LOG(LogHotGlobalShaderCommandlet,Display,TEXT("Compile Global Shader for %s, save to %s"),*PlatformNameStr,*SaveShaderToDir); + + { + SCOPED_NAMED_EVENT_TEXT("Compile Global Shader",FColor::Red); + FString Name = TEXT("Global"); + auto GlobalShaderCollectionProxy = UFlibHotCookerHelper::CreateCookShaderCollectionProxyByPlatform( + Name, + Platforms, + true, + true, + true, + SaveShaderToDir, + true + ); + if(GlobalShaderCollectionProxy.IsValid()) + { + GlobalShaderCollectionProxy->Init(); + } + // compile Global Shader + UFlibHotPatcherCoreHelper::SaveGlobalShaderMapFiles(UFlibHotPatcherCoreHelper::GetTargetPlatformsByNames(Platforms),SaveShaderToDir); + + UFlibShaderCodeLibraryHelper::WaitShaderCompilingComplete(); + UFlibHotPatcherCoreHelper::WaitForAsyncFileWrites(); + if(GlobalShaderCollectionProxy.IsValid()) + { + GlobalShaderCollectionProxy->Shutdown(); + } + } + + UE_LOG(LogHotGlobalShaderCommandlet,Display,TEXT("HotGlobalShader Misstion is Finished!")); + + if(FParse::Param(FCommandLine::Get(), TEXT("wait"))) + { + system("pause"); + } + + return 0; +} diff --git a/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/Cooker/HotGlobalShaderCommandlet.h b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/Cooker/HotGlobalShaderCommandlet.h new file mode 100644 index 0000000..bc64955 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/Cooker/HotGlobalShaderCommandlet.h @@ -0,0 +1,17 @@ +#pragma once + +#include "HotPatcherCommandletBase.h" +#include "Commandlets/Commandlet.h" +#include "HotGlobalShaderCommandlet.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogHotGlobalShaderCommandlet, All, All); + +UCLASS() +class UHotGlobalShaderCommandlet :public UHotPatcherCommandletBase +{ + GENERATED_BODY() + +public: + virtual int32 Main(const FString& Params)override; + virtual FString GetCmdletName()const override{ return TEXT("GlobalShaderCmdlet"); } +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/Cooker/HotSingleCookerCommandlet.cpp b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/Cooker/HotSingleCookerCommandlet.cpp new file mode 100644 index 0000000..dc53394 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/Cooker/HotSingleCookerCommandlet.cpp @@ -0,0 +1,74 @@ +#include "HotSingleCookerCommandlet.h" +#include "CommandletBase/CommandletHelper.h" +// engine header +#include "CoreMinimal.h" +#include "Cooker/MultiCooker/FSingleCookerSettings.h" +#include "Cooker/MultiCooker/SingleCookerProxy.h" +#include "HAL/ExceptionHandling.h" +#include "Misc/FileHelper.h" +#include "Misc/CommandLine.h" +#include "Misc/Paths.h" + +DEFINE_LOG_CATEGORY(LogHotSingleCookerCommandlet); + +int32 UHotSingleCookerCommandlet::Main(const FString& Params) +{ + Super::Main(Params); + SCOPED_NAMED_EVENT_TEXT("UHotSingleCookerCommandlet::Main",FColor::Red); + + UE_LOG(LogHotSingleCookerCommandlet, Display, TEXT("UHotSingleCookerCommandlet::Main")); + + if(CmdConfigPath.IsEmpty()) + { + return -1; + } + + ExportSingleCookerSetting = MakeShareable(new FSingleCookerSettings); + + FString JsonContent; + if (FPaths::FileExists(CmdConfigPath) && FFileHelper::LoadFileToString(JsonContent, *CmdConfigPath)) + { + THotPatcherTemplateHelper::TDeserializeJsonStringAsStruct(JsonContent,*ExportSingleCookerSetting); + } + + TMap KeyValues = THotPatcherTemplateHelper::GetCommandLineParamsMap(Params); + THotPatcherTemplateHelper::ReplaceProperty(*ExportSingleCookerSetting, KeyValues); + + CommandletHelper::ModifyTargetPlatforms(Params,TARGET_PLATFORMS_OVERRIDE,ExportSingleCookerSetting->CookTargetPlatforms,true); + if(ExportSingleCookerSetting->bDisplayConfig) + { + FString FinalConfig; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(*ExportSingleCookerSetting,FinalConfig); + } + + // if(IsRunningCommandlet() && ExportSingleCookerSetting->bPackageTracker && ExportSingleCookerSetting->bCookPackageTrackerAssets) + // { + // SCOPED_NAMED_EVENT_TEXT("SearchAllAssets",FColor::Red); + // // load asset registry + // FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + // AssetRegistryModule.Get().SearchAllAssets(true); + // } + + UE_LOG(LogHotSingleCookerCommandlet, Display, TEXT("Cooker %s Id %d,Assets Num %d"), *ExportSingleCookerSetting->MissionName,ExportSingleCookerSetting->MissionID,ExportSingleCookerSetting->CookAssets.Num()); + + GAlwaysReportCrash = false; + USingleCookerProxy* SingleCookerProxy = NewObject(); + SingleCookerProxy->AddToRoot(); + SingleCookerProxy->Init(ExportSingleCookerSetting.Get()); + bool bExportStatus = SingleCookerProxy->DoExport(); + + CommandletHelper::MainTick([SingleCookerProxy]()->bool + { + return SingleCookerProxy->IsFinsihed(); + }); + + SingleCookerProxy->Shutdown(); + UE_LOG(LogHotSingleCookerCommandlet,Display,TEXT("Single Cook Misstion %s %d is %s!"),*ExportSingleCookerSetting->MissionName,ExportSingleCookerSetting->MissionID,bExportStatus?TEXT("Successed"):TEXT("Failure")); + + if(FParse::Param(FCommandLine::Get(), TEXT("wait"))) + { + system("pause"); + } + + return bExportStatus ? 0 : -1; +} \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/Cooker/HotSingleCookerCommandlet.h b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/Cooker/HotSingleCookerCommandlet.h new file mode 100644 index 0000000..5ed4370 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/Cooker/HotSingleCookerCommandlet.h @@ -0,0 +1,29 @@ +#pragma once + +#include "Commandlets/Commandlet.h" +#include "CommandletBase/HotPatcherCommandletBase.h" +#include "Cooker/MultiCooker/FSingleCookerSettings.h" +#include "HotSingleCookerCommandlet.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogHotSingleCookerCommandlet, All, All); + +UCLASS() +class UHotSingleCookerCommandlet :public UHotPatcherCommandletBase +{ + GENERATED_BODY() + +public: + + virtual int32 Main(const FString& Params)override; + virtual FString GetCmdletName()const override + { + FString Result = TEXT("SingleCookerCmdlet"); + if(ExportSingleCookerSetting.IsValid()) + { + Result = FString::Printf(TEXT("%s_%s"),*Result,*ExportSingleCookerSetting.Get()->MissionName); + } + return Result; + } +protected: + TSharedPtr ExportSingleCookerSetting; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotPatcherCommandlet.cpp b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotPatcherCommandlet.cpp new file mode 100644 index 0000000..0ffcf78 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotPatcherCommandlet.cpp @@ -0,0 +1,84 @@ +#include "HotPatcherCommandlet.h" +// #include "CreatePatch/FExportPatchSettingsEx.h" +#include "CreatePatch/PatcherProxy.h" +#include "CommandletHelper.h" + +// engine header +#include "CoreMinimal.h" +#include "FlibHotPatcherCoreHelper.h" +#include "Misc/FileHelper.h" +#include "Misc/CommandLine.h" +#include "Misc/Paths.h" + +DEFINE_LOG_CATEGORY(LogHotPatcherCommandlet); + +int32 UHotPatcherCommandlet::Main(const FString& Params) +{ + Super::Main(Params); + + FCommandLine::Append(TEXT(" -buildmachine")); + GIsBuildMachine = true; + + UE_LOG(LogHotPatcherCommandlet, Display, TEXT("UHotPatcherCommandlet::Main")); + + FString config_path; + bool bStatus = FParse::Value(*Params, *FString(PATCHER_CONFIG_PARAM_NAME).ToLower(), config_path); + if (!bStatus) + { + UE_LOG(LogHotPatcherCommandlet, Error, TEXT("not -config=xxxx.json params.")); + return -1; + } + + config_path = UFlibPatchParserHelper::ReplaceMark(config_path); + if (!FPaths::FileExists(config_path)) + { + UE_LOG(LogHotPatcherCommandlet, Error, TEXT("cofnig file %s not exists."), *config_path); + return -1; + } + + FString JsonContent; + bool bExportStatus = false; + if (FFileHelper::LoadFileToString(JsonContent, *config_path)) + { + TSharedPtr ExportPatchSetting = MakeShareable(new FExportPatchSettings); + THotPatcherTemplateHelper::TDeserializeJsonStringAsStruct(JsonContent,*ExportPatchSetting); + // adaptor old version config + UFlibHotPatcherCoreHelper::AdaptorOldVersionConfig(ExportPatchSetting->GetAssetScanConfigRef(),JsonContent); + + TMap KeyValues = THotPatcherTemplateHelper::GetCommandLineParamsMap(Params); + THotPatcherTemplateHelper::ReplaceProperty(*ExportPatchSetting, KeyValues); + TArray AddPlatforms = CommandletHelper::ParserPlatforms(Params,ADD_PATCH_PLATFORMS); + + if(AddPlatforms.Num()) + { + for(auto& Platform:AddPlatforms) + { + ExportPatchSetting->PakTargetPlatforms.AddUnique(Platform); + } + } + CommandletHelper::ModifyTargetPlatforms(Params,TARGET_PLATFORMS_OVERRIDE,ExportPatchSetting->PakTargetPlatforms,true); + ExportPatchSetting->GetAssetScanConfigRef().AssetIncludeFilters.Append(CommandletHelper::ParserPatchFilters(Params,TEXT("AssetIncludeFilters"))); + ExportPatchSetting->GetAssetScanConfigRef().AssetIgnoreFilters.Append(CommandletHelper::ParserPatchFilters(Params,TEXT("AssetIgnoreFilters"))); + + FString FinalConfig; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(*ExportPatchSetting,FinalConfig); + // UE_LOG(LogHotPatcherCommandlet, Display, TEXT("%s"), *FinalConfig); + + + UPatcherProxy* PatcherProxy = NewObject(); + PatcherProxy->AddToRoot(); + PatcherProxy->Init(ExportPatchSetting.Get()); + PatcherProxy->OnPaking.AddStatic(&::CommandletHelper::ReceiveMsg); + PatcherProxy->OnShowMsg.AddStatic(&::CommandletHelper::ReceiveShowMsg); + bExportStatus = PatcherProxy->DoExport(); + + UE_LOG(LogHotPatcherCommandlet,Display,TEXT("Export Patch Misstion is %s, return %d!"),bExportStatus?TEXT("Successed"):TEXT("Failure"),(int32)!bExportStatus); + } + + if(FParse::Param(FCommandLine::Get(), TEXT("wait"))) + { + system("pause"); + } + + return (int32)!bExportStatus; +} diff --git a/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotPatcherCommandlet.h b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotPatcherCommandlet.h new file mode 100644 index 0000000..9ddd78a --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotPatcherCommandlet.h @@ -0,0 +1,17 @@ +#pragma once + +#include "HotPatcherCommandletBase.h" +#include "Commandlets/Commandlet.h" +#include "HotPatcherCommandlet.generated.h" + +// DECLARE_LOG_CATEGORY_EXTERN(LogHotPatcherCommandlet, All, All); + +UCLASS() +class UHotPatcherCommandlet :public UHotPatcherCommandletBase +{ + GENERATED_BODY() + +public: + + virtual int32 Main(const FString& Params)override; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotPatcherCommandletBase.cpp b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotPatcherCommandletBase.cpp new file mode 100644 index 0000000..ca6ca77 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotPatcherCommandletBase.cpp @@ -0,0 +1,129 @@ +#include "HotPatcherCommandletBase.h" +#include "CreatePatch/FExportReleaseSettings.h" +#include "CreatePatch/ReleaseProxy.h" +#include "CommandletHelper.h" + +// engine header +#include "CoreMinimal.h" +#include "HotPatcherCore.h" +#include "Misc/FileHelper.h" +#include "Misc/CommandLine.h" +#include "Misc/Paths.h" + +#if PLATFORM_WINDOWS && WITH_EDITOR +#include "Windows/AllowWindowsPlatformTypes.h" +#include "Windows.h" +#include "Windows/HideWindowsPlatformTypes.h" +#endif + +DEFINE_LOG_CATEGORY(LogHotPatcherCommandletBase); + + +static const bool bNoDDC = FParse::Param(FCommandLine::Get(), TEXT("NoDDC")); + +struct FObjectTrackerTagCleaner:public FPackageTrackerBase +{ + FObjectTrackerTagCleaner(UHotPatcherCommandletBase* InCommandletIns):CommandletIns(InCommandletIns){} + virtual void NotifyUObjectCreated(const UObjectBase* Object, int32 Index) override + { + auto ObjectIns = const_cast(static_cast(Object)); + if((ObjectIns && bNoDDC) || CommandletIns->IsSkipObject(ObjectIns)) + { + ObjectIns->ClearFlags(RF_NeedPostLoad); + ObjectIns->ClearFlags(RF_NeedPostLoadSubobjects); + } + } +protected: + UHotPatcherCommandletBase* CommandletIns; +}; + +FString UHotPatcherCommandletBase::GetCrashDir() +{ + return FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectDir(),TEXT("Saved/HotPatcher/Crashs"))); +} + +void UHotPatcherCommandletBase::CleanCrashDir() +{ + if(FPaths::DirectoryExists(GetCrashDir())) + { + IFileManager::Get().DeleteDirectory(*GetCrashDir(),false,true); + } +} + +void UHotPatcherCommandletBase::OnHandleSystemError() +{ + FString CrashTagFile = FPaths::Combine(GetCrashDir(),FString::Printf(TEXT("%s.txt"),*GetCmdletName())); + bool bSaveCrashTag = FFileHelper::SaveStringToFile(GetCmdletName(),*CrashTagFile); + UE_LOG(LogHotPatcherCommandletBase,Display,TEXT("%s Catch SystemError,Save CrashTag to %s(%s)"),*GetCmdletName(),*CrashTagFile,bSaveCrashTag ? TEXT("TRUE"):TEXT("FALSE")); +} + +void UHotPatcherCommandletBase::Update(const FString& Params) +{ + FString CommandletName; + bool bIsCommandlet = FParse::Value(FCommandLine::Get(), TEXT("-run="), CommandletName); + + if(GetDefault()->bServerlessCounterInCmdlet) + { + Counter = MakeShareable(new FCountServerlessWrapper); + FServerRequestInfo RequestInfo = FCountServerlessWrapper::MakeServerRequestInfo(); + auto ProjectInfo = FCountServerlessWrapper::MakeCurrentProject(); + ProjectInfo.PluginVersion = FString::Printf(TEXT("%d.%d"),GToolMainVersion,GToolPatchVersion); + + if(bIsCommandlet) + { + ProjectInfo.ProjectName = FString::Printf(TEXT("%s_%s"),*ProjectInfo.ProjectName,*CommandletName); + } + Counter->Init(RequestInfo,ProjectInfo); + Counter->Processor(); + } +} + +void UHotPatcherCommandletBase::MaybeMarkPackageAsAlreadyLoaded(UPackage* Package) +{ + if (bNoDDC || IsSkipPackage(Package)) + { + Package->SetPackageFlags(PKG_ReloadingForCooker); + Package->SetPackageFlags(PKG_FilterEditorOnly); + Package->SetPackageFlags(PKG_EditorOnly); + } +} + +int32 UHotPatcherCommandletBase::Main(const FString& Params) +{ + Update(Params); +#if SUPPORT_NO_DDC + // for Object Create Tracking,Optimize Asset searching, dont execute UObject::PostLoad + ObjectTrackerTagCleaner = MakeShareable(new FObjectTrackerTagCleaner(this)); + FCoreUObjectDelegates::PackageCreatedForLoad.AddUObject(this,&UHotPatcherCommandletBase::MaybeMarkPackageAsAlreadyLoaded); +#endif + +#if PLATFORM_WINDOWS && WITH_EDITOR + { + SetPriorityClass(GetCurrentProcess(),REALTIME_PRIORITY_CLASS); + UE_LOG(LogHotPatcher,Display,TEXT("Set Commandlet Priority to REALTIME_PRIORITY_CLASS.")); + } +#endif + + bool bStatus = FParse::Value(*Params, *FString(PATCHER_CONFIG_PARAM_NAME).ToLower(), CmdConfigPath); + if (!bStatus) + { + UE_LOG(LogHotPatcherCommandletBase, Warning, TEXT("not -config=xxxx.json params.")); + return -1; + } + CmdConfigPath = UFlibPatchParserHelper::ReplaceMark(CmdConfigPath); + if (bStatus && !FPaths::FileExists(CmdConfigPath)) + { + UE_LOG(LogHotPatcherCommandletBase, Error, TEXT("cofnig file %s not exists."), *CmdConfigPath); + return -1; + } + + FCoreDelegates::OnHandleSystemError.AddLambda([this](){ this->OnHandleSystemError(); }); + + if(IsRunningCommandlet() && !FParse::Param(FCommandLine::Get(), TEXT("NoSearchAllAssets"))) + { + SCOPED_NAMED_EVENT_TEXT("SearchAllAssets",FColor::Red); + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + AssetRegistryModule.Get().SearchAllAssets(true); + } + return 0; +} \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotReleaseCommandlet.cpp b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotReleaseCommandlet.cpp new file mode 100644 index 0000000..67f1dc8 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotReleaseCommandlet.cpp @@ -0,0 +1,140 @@ +#include "HotReleaseCommandlet.h" +#include "CreatePatch/FExportReleaseSettings.h" +#include "CreatePatch/ReleaseProxy.h" +#include "CommandletHelper.h" +#include "FlibHotPatcherCoreHelper.h" + +// engine header +#include "CoreMinimal.h" +#include "Misc/FileHelper.h" +#include "Misc/CommandLine.h" +#include "Misc/Paths.h" + +DEFINE_LOG_CATEGORY(LogHotReleaseCommandlet); + + +TArray ParserPlatformFilesByCmd(const FString& Commandline,const FString& Key,TFunction FileCallback) +{ + TArray result; + TMap KeyValues = THotPatcherTemplateHelper::GetCommandLineParamsMap(Commandline); + if(KeyValues.Find(Key)) + { + FString AddPakListInfo = *KeyValues.Find(Key); + TArray PlatformPakLists; + AddPakListInfo.ParseIntoArray(PlatformPakLists,TEXT(",")); + + for(auto& PlatformPakList:PlatformPakLists) + { + TArray PlatformPakListToken; + PlatformPakList.ParseIntoArray(PlatformPakListToken,TEXT("+")); + if(PlatformPakListToken.Num() >= 2) + { + FString PlatformName = PlatformPakListToken[0]; + FPlatformPakListFiles PlatformPakListItem; + THotPatcherTemplateHelper::GetEnumValueByName(PlatformName,PlatformPakListItem.TargetPlatform); + for(int32 index=1;index ParserPlatformPakList(const FString& Commandline) +{ + TArray result; + result.Append(ParserPlatformFilesByCmd(Commandline,ADD_PLATFORM_PAK_LIST,[](FPlatformPakListFiles& PlatformPakListItem,const FFilePath& File) + { + PlatformPakListItem.PakResponseFiles.Add(File); + })); + result.Append(ParserPlatformFilesByCmd(Commandline,ADD_PLATFORM_PAK_FILES,[](FPlatformPakListFiles& PlatformPakListItem,const FFilePath& File) + { + PlatformPakListItem.PakFiles.Add(File); + })); + return result; +} + +int32 UHotReleaseCommandlet::Main(const FString& Params) +{ + Super::Main(Params); + UE_LOG(LogHotReleaseCommandlet, Display, TEXT("UHotReleaseCommandlet::Main:%s"), *Params); + + FString config_path; + bool bStatus = FParse::Value(*Params, *FString(PATCHER_CONFIG_PARAM_NAME).ToLower(), config_path); + if (!bStatus) + { + UE_LOG(LogHotReleaseCommandlet, Warning, TEXT("not -config=xxxx.json params.")); + // return -1; + } + config_path = UFlibPatchParserHelper::ReplaceMark(config_path); + if (bStatus && !FPaths::FileExists(config_path)) + { + UE_LOG(LogHotReleaseCommandlet, Error, TEXT("config file %s not exists."), *config_path); + return -1; + } + if(IsRunningCommandlet()) + { + // load asset registry + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + AssetRegistryModule.Get().SearchAllAssets(true); + } + + TSharedPtr ExportReleaseSetting = MakeShareable(new FExportReleaseSettings); + + FString JsonContent; + if (FPaths::FileExists(config_path) && FFileHelper::LoadFileToString(JsonContent, *config_path)) + { + THotPatcherTemplateHelper::TDeserializeJsonStringAsStruct(JsonContent,*ExportReleaseSetting); + // adaptor old version config + UFlibHotPatcherCoreHelper::AdaptorOldVersionConfig(ExportReleaseSetting->GetAssetScanConfigRef(),JsonContent); + } + + TMap KeyValues = THotPatcherTemplateHelper::GetCommandLineParamsMap(Params); + THotPatcherTemplateHelper::ReplaceProperty(*ExportReleaseSetting, KeyValues); + + // 从命令行分析PlatformPakList + TArray ReadPakList = ParserPlatformPakList(Params); + if(ReadPakList.Num()) + { + ExportReleaseSetting->PlatformsPakListFiles = ReadPakList; + } + + if(ReadPakList.Num() && ExportReleaseSetting->IsBackupMetadata()) + { + for(const auto& PlatformPakList:ReadPakList) + { + ExportReleaseSetting->BackupMetadataPlatforms.AddUnique(PlatformPakList.TargetPlatform); + } + } + + FString FinalConfig; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(*ExportReleaseSetting,FinalConfig); + UE_LOG(LogHotReleaseCommandlet, Display, TEXT("%s"), *FinalConfig); + + UReleaseProxy* ReleaseProxy = NewObject(); + ReleaseProxy->AddToRoot(); + ReleaseProxy->Init(ExportReleaseSetting.Get()); + ReleaseProxy->OnPaking.AddStatic(&::CommandletHelper::ReceiveMsg); + ReleaseProxy->OnShowMsg.AddStatic(&::CommandletHelper::ReceiveShowMsg); + bool bExportStatus = ReleaseProxy->DoExport(); + + UE_LOG(LogHotReleaseCommandlet,Display,TEXT("Export Release Misstion is %s!"),bExportStatus?TEXT("Successed"):TEXT("Failure")); + + if(FParse::Param(FCommandLine::Get(), TEXT("wait"))) + { + system("pause"); + } + + return (int32)!bExportStatus; +} diff --git a/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotReleaseCommandlet.h b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotReleaseCommandlet.h new file mode 100644 index 0000000..5ae14e3 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Classes/Commandlets/HotReleaseCommandlet.h @@ -0,0 +1,16 @@ +#pragma once +#include "HotPatcherCommandletBase.h" +#include "Commandlets/Commandlet.h" +#include "HotReleaseCommandlet.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogHotReleaseCommandlet, All, All); + +UCLASS() +class UHotReleaseCommandlet :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 new file mode 100644 index 0000000..2f50b40 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/HotPatcherCore.Build.cs @@ -0,0 +1,198 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System; +using System.Collections.Generic; +using System.IO; + +public class HotPatcherCore : ModuleRules +{ + public HotPatcherCore(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + bLegacyPublicIncludePaths = false; + OptimizeCode = CodeOptimization.InShippingBuildsOnly; + + PublicIncludePaths.AddRange( + new string[] { + Path.Combine(ModuleDirectory,"Public/CommandletBase") + // ... add public include paths required here ... + } + ); + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "UnrealEd", + "UMG", + "UMGEditor", + "Core", + "Json", + "LevelSequence", + "ContentBrowser", + "SandboxFile", + "JsonUtilities", + "TargetPlatform", + "DesktopPlatform", + "Projects", + "Settings", + "HTTP", + "RHI", + "EngineSettings", + "AssetRegistry", + "PakFileUtilities", + "HotPatcherRuntime", + "BinariesPatchFeature" + // ... add other public dependencies that you statically link with here ... + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "DesktopPlatform", + "InputCore", + "CoreUObject", + "Engine", + "Sockets", + "DerivedDataCache" + // ... 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"); + } + + if (Target.Version.MajorVersion > 4 || Target.Version.MinorVersion > 23) + { + PublicDependencyModuleNames.AddRange(new string[]{ + "TraceLog" + }); + } + + + // // only in UE5 + if (Target.Version.MajorVersion > 4) + { + PublicDependencyModuleNames.AddRange(new string[] + { + "DeveloperToolSettings" + }); + } + + switch (Target.Configuration) + { + case UnrealTargetConfiguration.Debug: + { + PublicDefinitions.Add("COMPILER_CONFIGURATION_NAME=\"Debug\""); + break; + } + case UnrealTargetConfiguration.DebugGame: + { + PublicDefinitions.Add("COMPILER_CONFIGURATION_NAME=\"DebugGame\""); + break; + } + case UnrealTargetConfiguration.Development: + { + PublicDefinitions.Add("COMPILER_CONFIGURATION_NAME=\"Development\""); + break; + } + default: + { + PublicDefinitions.Add("COMPILER_CONFIGURATION_NAME=\"\""); + break; + } + }; + + System.Func AddPublicDefinitions = (string MacroName,bool bEnable) => + { + PublicDefinitions.Add(string.Format("{0}={1}",MacroName, bEnable ? 1 : 0)); + return true; + }; + + bool bIOStoreSupport = Target.Version.MajorVersion > 4 || Target.Version.MinorVersion > 25; + if (bIOStoreSupport) + { + PublicDependencyModuleNames.AddRange(new string[] + { + "IoStoreUtilities" + }); + } + AddPublicDefinitions("WITH_IO_STORE_SUPPORT", bIOStoreSupport); + AddPublicDefinitions("ENABLE_COOK_LOG", true); + AddPublicDefinitions("ENABLE_COOK_ENGINE_MAP", false); + AddPublicDefinitions("ENABLE_COOK_PLUGIN_MAP", false); + BuildVersion Version; + BuildVersion.TryRead(BuildVersion.GetDefaultFileName(), out Version); + AddPublicDefinitions("WITH_EDITOR_SECTION", Version.MajorVersion > 4 || Version.MinorVersion > 24); + System.Console.WriteLine("MajorVersion {0} MinorVersion: {1} PatchVersion {2}",Target.Version.MajorVersion,Target.Version.MinorVersion,Target.Version.PatchVersion); + + // !!! Please make sure to modify the engine if necessary, otherwise it will cause a crash + AddPublicDefinitions("SUPPORT_NO_DDC", true); + + // Game feature + bool bEnableGameFeature = true; + if (bEnableGameFeature || (Target.Version.MajorVersion > 4 || Target.Version.MinorVersion > 26)) + { + PublicDependencyModuleNames.AddRange(new string[] + { + // "GameFeatures", + // "ModularGameplay", + }); + } + + bool bEnablePackageContext = true; + AddPublicDefinitions("WITH_PACKAGE_CONTEXT", (Version.MajorVersion > 4 || Version.MinorVersion > 23) && bEnablePackageContext); + if (Version.MajorVersion > 4 || Version.MinorVersion > 26) + { + PublicDependencyModuleNames.AddRange(new string[] + { + "IoStoreUtilities", + // "UnrealEd" + }); + } + + bool bGenerateChunksManifest = false; + AddPublicDefinitions("GENERATE_CHUNKS_MANIFEST", bGenerateChunksManifest); + if (bGenerateChunksManifest) + { + PublicIncludePaths.AddRange(new string[] + { + Path.Combine(EngineDirectory,"Source/Editor/UnrealEd/Public"), + Path.Combine(EngineDirectory,"Source/Editor/UnrealEd/Private"), + }); + } + + if (Version.MajorVersion > 4) + { + PublicIncludePaths.AddRange(new List() + { + // Path.Combine(EngineDirectory,"Source/Developer/IoStoreUtilities/Internal"), + // Path.Combine(EngineDirectory,"Source/Editor/UnrealEd/Private/Cooker"), + // Path.Combine(EngineDirectory,"Source/Editor/UnrealEd/Private"), + Path.Combine(EngineDirectory,"Source/Runtime/CoreUObject/Internal/Serialization"), + }); + } + + PublicDefinitions.AddRange(new string[] + { + "TOOL_NAME=\"HotPatcher\"", + "CURRENT_VERSION_ID=81", + "CURRENT_PATCH_ID=0", + "REMOTE_VERSION_FILE=\"https://imzlp.com/opensource/version.json\"" + }); + } +} diff --git a/HotPatcher/Source/HotPatcherCore/Private/Cooker/MultiCooker/FSingleCookerSettings.cpp b/HotPatcher/Source/HotPatcherCore/Private/Cooker/MultiCooker/FSingleCookerSettings.cpp new file mode 100644 index 0000000..458ac0d --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Private/Cooker/MultiCooker/FSingleCookerSettings.cpp @@ -0,0 +1,31 @@ +#include "Cooker/MultiCooker/FSingleCookerSettings.h" +#include "FlibPatchParserHelper.h" + +FSingleCookerSettings::FSingleCookerSettings() +{ + OverrideNumberOfAssetsPerFrame.Add(UWorld::StaticClass(),2); +} + +FString FSingleCookerSettings::GetStorageCookedAbsDir() const +{ + return UFlibPatchParserHelper::ReplaceMark(StorageCookedDir); +} + +FString FSingleCookerSettings::GetStorageMetadataAbsDir() const +{ + return UFlibPatchParserHelper::ReplaceMark(StorageMetadataDir); +} + +bool FSingleCookerSettings::IsSkipAsset(const FString& PackageName) +{ + bool bRet = false; + for(const auto& SkipCookContent:SkipCookContents) + { + if(PackageName.StartsWith(SkipCookContent)) + { + bRet = true; + break; + } + } + return bRet; +} diff --git a/HotPatcher/Source/HotPatcherCore/Private/Cooker/MultiCooker/FlibHotCookerHelper.cpp b/HotPatcher/Source/HotPatcherCore/Private/Cooker/MultiCooker/FlibHotCookerHelper.cpp new file mode 100644 index 0000000..9642872 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Private/Cooker/MultiCooker/FlibHotCookerHelper.cpp @@ -0,0 +1,114 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Cooker/MultiCooker/FlibHotCookerHelper.h" + +#include "FlibHotPatcherCoreHelper.h" +#include "FlibPatchParserHelper.h" +#include "Cooker/MultiCooker/FCookShaderCollectionProxy.h" +#include "Interfaces/IPluginManager.h" +#include "ShaderLibUtils/FlibShaderCodeLibraryHelper.h" + +// FString UFlibHotCookerHelper::GetMultiCookerBaseDir() +// { +// FString SaveConfigTo = FPaths::ConvertRelativePathToFull( +// FPaths::Combine( +// FPaths::ProjectSavedDir(), +// TEXT("HotPatcher/MultiCooker"), +// FApp::GetProjectName() +// )); +// return SaveConfigTo; +// } + +// FString UFlibHotCookerHelper::GetCookerProcConfigPath(const FString& MissionName, int32 MissionID) +// { +// FString SaveConfigTo = FPaths::Combine( +// UFlibHotCookerHelper::GetMultiCookerBaseDir(), +// // FString::Printf(TEXT("%s_Cooker_%d.json"),*MissionName,MissionID) +// FString::Printf(TEXT("%s.json"),*MissionName) +// ); +// return SaveConfigTo; +// } + +FString UFlibHotCookerHelper::GetCookedDir() +{ + return FPaths::Combine( + TEXT("[PROJECTDIR]"), + TEXT("Saved"),TEXT("Cooked")); +} + +FString UFlibHotCookerHelper::GetCookerBaseDir() +{ + FString SaveConfigTo = FPaths::Combine( + TEXT("[PROJECTDIR]"), + TEXT("Saved"), + TEXT("HotPatcher/MultiCooker"), + FApp::GetProjectName() + ); + return SaveConfigTo; +} + +FString UFlibHotCookerHelper::GetCookerProcFailedResultPath(const FString& BaseDir,const FString& MissionName, int32 MissionID) +{ + FString SaveConfigTo = FPaths::Combine( + BaseDir, + FString::Printf(TEXT("%s_FailedAssets.json"),*MissionName) + ); + return SaveConfigTo; +} + +FString UFlibHotCookerHelper::GetProfilingCmd() +{ + return FString::Printf(TEXT("-tracehost=127.0.0.1 -trace=cpu,memory,loadtime -statnamedevents implies -llm")); +} + +TSharedPtr UFlibHotCookerHelper::CreateCookShaderCollectionProxyByPlatform( + const FString& ShaderLibraryName, + TArray Platforms, + bool bShareShader, + bool bNativeShader, + bool bMaster, + const FString& InSavePath, + bool bCleanSavePath) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibHotCookerHelper::CreateCookShaderCollectionProxyByPlatform",FColor::Red); + TSharedPtr CookShaderCollection; + TArray PlatformNames; + for(const auto& Platform:Platforms) + { + PlatformNames.AddUnique(THotPatcherTemplateHelper::GetEnumNameByValue(Platform)); + } + + FString ActualLibraryName = UFlibShaderCodeLibraryHelper::GenerateShaderCodeLibraryName(FApp::GetProjectName(),false); + if(bCleanSavePath) + { + UFlibHotPatcherCoreHelper::DeleteDirectory(InSavePath); + } + CookShaderCollection = MakeShareable( + new FCookShaderCollectionProxy( + PlatformNames, + ShaderLibraryName, + bShareShader, + bNativeShader, + bMaster, + InSavePath + )); + + return CookShaderCollection; +} + +bool UFlibHotCookerHelper::IsAppleMetalPlatform(ITargetPlatform* TargetPlatform) +{ + SCOPED_NAMED_EVENT_TEXT("IsAppleMetalPlatform",FColor::Red); + bool bIsMatched = false; + TArray ApplePlatforms = {TEXT("IOS"),TEXT("Mac"),TEXT("TVOS")}; + for(const auto& Platform:ApplePlatforms) + { + if(TargetPlatform->PlatformName().StartsWith(Platform,ESearchCase::IgnoreCase)) + { + bIsMatched = true; + break; + } + } + return bIsMatched; +} \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Private/Cooker/MultiCooker/SingleCookerProxy.cpp b/HotPatcher/Source/HotPatcherCore/Private/Cooker/MultiCooker/SingleCookerProxy.cpp new file mode 100644 index 0000000..c13fc16 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Private/Cooker/MultiCooker/SingleCookerProxy.cpp @@ -0,0 +1,859 @@ +#include "Cooker/MultiCooker/SingleCookerProxy.h" + +#include "FlibHotPatcherCoreHelper.h" +#include "HotPatcherCore.h" +#include "HotPatcherRuntime.h" +#include "Cooker/MultiCooker/FlibHotCookerHelper.h" +#include "ShaderCompiler.h" +#include "UObject/UObjectHash.h" +#include "UObject/UObjectIterator.h" +#include "Async/ParallelFor.h" +#include "ShaderLibUtils/FlibShaderCodeLibraryHelper.h" +#include "ThreadUtils/FThreadUtils.hpp" +#include "Cooker/MultiCooker/FCookShaderCollectionProxy.h" +#include "Engine/Engine.h" +#include "Misc/ScopeExit.h" +#include "Engine/AssetManager.h" +#include "Interfaces/ITargetPlatform.h" + +#if WITH_PACKAGE_CONTEXT +// // engine header +#include "UObject/SavePackage.h" +#endif + +#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION > 25 +#include "Serialization/BulkDataManifest.h" +#endif + + +bool IsAlwayPostLoadClasses(UPackage* Package, UObject* Object) +{ + return true; +} + +void FFreezePackageTracker::NotifyUObjectCreated(const UObjectBase* Object, int32 Index) +{ + // UObject* ObjectOuter = Object->GetOuter(); + auto ObjectOuter = const_cast(static_cast(Object)); + UPackage* Package = ObjectOuter ? ObjectOuter->GetOutermost() : nullptr; + if(Package) + { + + FName AssetPathName = FName(*Package->GetPathName()); + if(!CookerAssetsSet.Contains(AssetPathName) && AllAssetsSet.Contains(AssetPathName) && !IsAlwayPostLoadClasses(Package, ObjectOuter)) + { + PackageObjectsMap.FindOrAdd(AssetPathName).Add(ObjectOuter); + FreezeObjects.Add(ObjectOuter); + ObjectOuter->ClearFlags(RF_NeedPostLoad); + ObjectOuter->ClearFlags(RF_NeedPostLoadSubobjects); + } + } +} +#define NO_POSTLOAD_CACHE_DDC_OPTION TEXT("-NoPostLoadCacheDDC") +void USingleCookerProxy::Init(FPatcherEntitySettingBase* InSetting) +{ + SCOPED_NAMED_EVENT_TEXT("Init",FColor::Red); + Super::Init(InSetting); + // IConsoleVariable* StreamableDelegateDelayFrames = IConsoleManager::Get().FindConsoleVariable(TEXT("s.StreamableDelegateDelayFrames")); + // 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) + { + PlatformSavePackageContexts = GetSettingObject()->PlatformSavePackageContexts; + } + else + { + PlatformSavePackageContexts = UFlibHotPatcherCoreHelper::CreatePlatformsPackageContexts( + GetSettingObject()->CookTargetPlatforms, + GetSettingObject()->IoStoreSettings.bIoStore, + GetSettingObject()->GetStorageCookedAbsDir() + ); + } +#endif + InitShaderLibConllections(); + + // cook package tracker + if(GetSettingObject()->bPackageTracker) + { + // all cooker assets + ExixtPackagePathSet.PackagePaths.Append(GetSettingObject()->SkipLoadedAssets); + // current cooker assets + ExixtPackagePathSet.PackagePaths.Append(GetCookerAssets()); + PackageTracker = MakeShareable(new FPackageTracker(ExixtPackagePathSet.PackagePaths)); + OtherCookerPackageTracker = MakeShareable(new FOtherCookerPackageTracker(GetCookerAssets(),GetSettingObject()->SkipLoadedAssets)); + // FreezePackageTracker = MakeShareable(new FFreezePackageTracker(GetCookerAssets(),GetSettingObject()->SkipLoadedAssets)); + } + + UFlibHotPatcherCoreHelper::DeleteDirectory(GetSettingObject()->GetStorageMetadataAbsDir()); + + GetCookFailedAssetsCollection().MissionName = GetSettingObject()->MissionName; + GetCookFailedAssetsCollection().MissionID = GetSettingObject()->MissionID; + GetCookFailedAssetsCollection().CookFailedAssets.Empty(); + OnAssetCooked.AddUObject(this,&USingleCookerProxy::OnAssetCookedHandle); +} + +int32 USingleCookerProxy::MakeCookQueue(FCookCluster& InCluser) +{ + SCOPED_NAMED_EVENT_TEXT("MakeCookQueue",FColor::Red); + int32 MakeClusterCount = 0; + FString DumpCookerInfo; + DumpCookerInfo.Append(TEXT("\n-----------------------------Dump Cooker-----------------------------\n")); + DumpCookerInfo.Append(FString::Printf(TEXT("\tTotal Asset: %d\n"),InCluser.AssetDetails.Num())); + DumpCookerInfo.Append(FString::Printf(TEXT("\tbForceCookInOneFrame: %s\n"),GetSettingObject()->bForceCookInOneFrame ? TEXT("true"):TEXT("false"))); + FString PlatformsStr; + for(auto Platform:InCluser.Platforms) + { + PlatformsStr += THotPatcherTemplateHelper::GetEnumNameByValue(Platform); + PlatformsStr.Append(TEXT(",")); + } + PlatformsStr.RemoveFromEnd(TEXT(",")); + DumpCookerInfo.Append(FString::Printf(TEXT("\tTarget Platforms: %s\n"),*PlatformsStr)); + + const int32 NumberOfAssetsPerFrame = GetSettingObject()->GetNumberOfAssetsPerFrame(); + + for(auto Class:GetPreCacheClasses()) + { + 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); + + 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; + } + } + 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) + { + 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; +} + +void USingleCookerProxy::CleanClusterCachedPlatformData(const FCookCluster& CookCluster) +{ + SCOPED_NAMED_EVENT_TEXT("CleanClusterCachedPlatformData",FColor::Red); + TArray TargetPlatforms = UFlibHotPatcherCoreHelper::GetTargetPlatformsByNames(CookCluster.Platforms); + TArray PreCachePackages = UFlibAssetManageHelper::LoadPackagesForCooking(CookCluster.AsSoftObjectPaths(),GetSettingObject()->bConcurrentSave); + for(auto Package:PreCachePackages) + { + TArray ExportMap; + GetObjectsWithOuter(Package,ExportMap,true); + for(const auto& ExportObj:ExportMap) + { +#if ENGINE_MINOR_VERSION < 26 + FScopedNamedEvent CacheExportEvent(FColor::Red,*FString::Printf(TEXT("%s"),*ExportObj->GetName())); +#endif + if (ExportObj->HasAnyFlags(RF_Transient)) + { + // UE_LOG(LogHotPatcherCoreHelper, Display, TEXT("%s is PreCached."),*ExportObj->GetFullName()); + continue; + } + ExportObj->ClearAllCachedCookedPlatformData(); + } +#if ENGINE_MAJOR_VERSION > 4 + Package->MarkAsGarbage(); +#else + Package->MarkPendingKill(); +#endif + } +} + +void USingleCookerProxy::PreGeneratePlatformData(const FCookCluster& CookCluster) +{ + SCOPED_NAMED_EVENT_TEXT("PreGeneratePlatformData",FColor::Red); + FExecTimeRecoder PreGeneratePlatformDataTimer(TEXT("PreGeneratePlatformData")); + TArray TargetPlatforms = UFlibHotPatcherCoreHelper::GetTargetPlatformsByNames(CookCluster.Platforms); + bool bConcurrentSave = GetSettingObject()->bConcurrentSave; + TSet ProcessedObjects; + TSet PendingCachePlatformDataObjects; + + auto PreCachePlatformDataForPackages = [&](TArray& PreCachePackages) + { + uint32 TotalPackagesNum = PreCachePackages.Num(); + for(int32 Index = PreCachePackages.Num() - 1 ;Index >= 0;--Index) + { + UPackage* CurrentPackage = PreCachePackages[Index]; + if(GCookLog) + { + UE_LOG(LogHotPatcher,Log,TEXT("PreCache %s, pending %d total %d"),*CurrentPackage->GetPathName(),PreCachePackages.Num()-1,TotalPackagesNum); + } + + UFlibHotPatcherCoreHelper::CacheForCookedPlatformData( + TArray{CurrentPackage}, + TargetPlatforms, + ProcessedObjects, + PendingCachePlatformDataObjects, + bConcurrentSave, + GetSettingObject()->bWaitEachAssetCompleted, + [&](UPackage* Package,UObject* ExportObj) + { + if(FreezePackageTracker.IsValid() && FreezePackageTracker->IsFreezed(ExportObj)) + { + ExportObj->SetFlags(RF_NeedPostLoad); + ExportObj->SetFlags(RF_NeedPostLoadSubobjects); + ExportObj->ConditionalPostLoad(); + } + }); + PreCachePackages.RemoveAtSwap(Index,1,false); + } + }; + + TArray PreCachePackages = UFlibAssetManageHelper::LoadPackagesForCooking(CookCluster.AsSoftObjectPaths(),GetSettingObject()->bConcurrentSave); + + if(CookCluster.bPreGeneratePlatformData) + { + PreCachePlatformDataForPackages(PreCachePackages); + } + UFlibHotPatcherCoreHelper::WaitObjectsCachePlatformDataComplete(ProcessedObjects,PendingCachePlatformDataObjects,TargetPlatforms); +} + +void USingleCookerProxy::DumpCluster(const FCookCluster& CookCluster, bool bWriteToLog) +{ + SCOPED_NAMED_EVENT_TEXT("DumpCluster",FColor::Red); + FString Dumper; + Dumper.Append("\n--------------------Dump Cook Cluster--------------------\n"); + Dumper.Append(FString::Printf(TEXT("\tAsset Number: %d\n"),CookCluster.AssetDetails.Num())); + + FString PlatformsStr; + { + for(const auto& Platform:CookCluster.Platforms) + { + PlatformsStr = FString::Printf(TEXT("%s+%s"),*PlatformsStr,*THotPatcherTemplateHelper::GetEnumNameByValue(Platform)); + } + PlatformsStr.RemoveFromStart(TEXT("+")); + } + Dumper.Append(FString::Printf(TEXT("\tCook Platforms: %s\n"),*PlatformsStr)); + Dumper.Append(FString::Printf(TEXT("\tPreGeneratePlatformData: %s\n"),CookCluster.bPreGeneratePlatformData?TEXT("true"):TEXT("false"))); + Dumper.Append(TEXT("\tCluster Assets:\n")); + for(const auto& Asset:CookCluster.AssetDetails) + { + Dumper.Append(FString::Printf(TEXT("\t%s %s\n"),*Asset.AssetType.ToString(),*Asset.PackagePath.ToString())); + } + Dumper.Append("---------------------------------------------------------\n"); + if(bWriteToLog) + { + UE_LOG(LogHotPatcher,Display,TEXT("%s"),*Dumper); + } +} + +void USingleCookerProxy::ExecCookCluster(const FCookCluster& CookCluster) +{ + SCOPED_NAMED_EVENT_TEXT("ExecCookCluster",FColor::Red); + + // for GC + { + UE_LOG(LogHotPatcher,Display,TEXT("ExecuteCookCluster Try GC...")); + GEngine->ForceGarbageCollection(false); + CollectGarbage(RF_NoFlags, false); + } + + CookedClusterCount++; + UE_LOG(LogHotPatcher,Display,TEXT("ExecuteCookCluster %d with %d assets, total cluster %d"),CookedClusterCount,CookCluster.AssetDetails.Num(),ClusterCount); + + FExecTimeRecoder ExecCookClusterTimer(TEXT("ExecCookCluster")); + + if(!CookCluster.AssetDetails.Num()) + { + 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); + +#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; + // pre cahce platform data + if(CookCluster.bPreGeneratePlatformData) + { + PreGeneratePlatformData(CookCluster); + } + + // for cooking + { + TMap> PackageCookedSavePaths; + for(const auto& Package:PreCachePackages) + { + FName PackagePathName = *Package->GetPathName(); + PackageCookedSavePaths.Add(PackagePathName,TMap{}); + for(auto Platform:TargetPlatforms) + { + FString CookedSavePath = UFlibHotPatcherCoreHelper::GetAssetCookedSavePath(CookBaseDir,PackagePathName.ToString(), Platform->PlatformName()); + PackageCookedSavePaths.Find(PackagePathName)->Add(*Platform->PlatformName(),CookedSavePath); + } + } + + TMap PlatformMaps; + for(auto Platform:TargetPlatforms) + { + ETargetPlatform EnumPlatform; + THotPatcherTemplateHelper::GetEnumValueByName(*Platform->PlatformName(),EnumPlatform); + PlatformMaps.Add(EnumPlatform,Platform); + } + + auto CookPackageLambda = [&](int32 AssetIndex) + { + // FExecTimeRecoder CookTimer(PreCachePackages[AssetIndex]->GetFullName()); + UFlibHotPatcherCoreHelper::CookPackage( + PreCachePackages[AssetIndex], + PlatformMaps, + CookCluster.CookActionCallback, +#if WITH_PACKAGE_CONTEXT + SavePackageContextsNameMapping, +#endif + *PackageCookedSavePaths.Find(*PreCachePackages[AssetIndex]->GetPathName()), + GetSettingObject()->bConcurrentSave + ); + }; + + if(bCanConcurrentSave) + { + for(auto& Platform:TargetPlatforms) + { + ETargetPlatform TargetPlatform; + THotPatcherTemplateHelper::GetEnumValueByName(Platform->PlatformName(),TargetPlatform); + } + GIsSavingPackage = true; + ParallelFor(PreCachePackages.Num(), CookPackageLambda,GForceSingleThread ? true : !bCanConcurrentSave); + GIsSavingPackage = false; + } + else + { + for(int32 index = 0;indexbForceCookInOneFrame|| !IsAlready()) + { + return; + } + + if(!CookCluserQueue.IsEmpty()) + { + FCookCluster CookCluster; + CookCluserQueue.Dequeue(CookCluster); + ExecCookCluster(CookCluster); + } + static bool bCookedPackageTracker = false; + + if(!bCookedPackageTracker && CookCluserQueue.IsEmpty() && GetSettingObject()->bPackageTracker && GetSettingObject()->bCookPackageTrackerAssets) + { + FCookCluster PackageTrackerCluster = GetPackageTrackerAsCluster(); + if(PackageTrackerCluster.AssetDetails.Num()) + { + ClusterCount += MakeCookQueue(PackageTrackerCluster); + bCookedPackageTracker = true; + } + } +} + +TStatId USingleCookerProxy::GetStatId() const +{ + RETURN_QUICK_DECLARE_CYCLE_STAT(USingleCookerProxy, STATGROUP_Tickables); +} + +bool USingleCookerProxy::IsTickable() const +{ + return IsAlready() && !const_cast(this)->GetSettingObject()->bForceCookInOneFrame; +} + +void USingleCookerProxy::Shutdown() +{ + SCOPED_NAMED_EVENT_TEXT("USingleCookerProxy::Shutdown",FColor::Red); + { + SCOPED_NAMED_EVENT_TEXT("USingleCookerProxy::WaitCookerFinished",FColor::Red); + // Wait for all shaders to finish compiling + UFlibShaderCodeLibraryHelper::WaitShaderCompilingComplete(); + UFlibHotPatcherCoreHelper::WaitForAsyncFileWrites(); + } + + ShutdowShaderLibCollections(); + FString StorageMetadataAbsDir = GetSettingObject()->GetStorageMetadataAbsDir(); + // serialize cook failed assets to json + if(GetCookFailedAssetsCollection().CookFailedAssets.Num()) + { + FString FailedJsonString; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(GetCookFailedAssetsCollection(),FailedJsonString); + FString SaveTo = UFlibHotCookerHelper::GetCookerProcFailedResultPath(StorageMetadataAbsDir,GetSettingObject()->MissionName,GetSettingObject()->MissionID); + SaveTo = UFlibPatchParserHelper::ReplaceMark(SaveTo); + UE_LOG(LogHotPatcher,Warning,TEXT("Single Cooker Proxy Cook Failed Assets %s:\n%s\nSerialize to %s"),*GetSettingObject()->MissionName,*FailedJsonString,*SaveTo); + FFileHelper::SaveStringToFile(FailedJsonString,*SaveTo); + } + + if(PackageTracker) + { + auto SerializePackageSetToString = [](const TSet& Packages)->FString + { + FString OutString; + FPackagePathSet AdditionalPackageSet; + AdditionalPackageSet.PackagePaths.Append(Packages); + if(AdditionalPackageSet.PackagePaths.Num()) + { + THotPatcherTemplateHelper::TSerializeStructAsJsonString(AdditionalPackageSet,OutString); + } + return OutString; + }; + const TSet& AllLoadedPackagePaths = PackageTracker->GetLoadedPackages(); + const TSet& AdditionalPackageSet = PackageTracker->GetPendingPackageSet(); + const TSet LoadedOtherCookerAssets = OtherCookerPackageTracker->GetLoadOtherCookerAssets(); + + if(AllLoadedPackagePaths.Num()) + { + FFileHelper::SaveStringToFile(SerializePackageSetToString(AllLoadedPackagePaths),*FPaths::Combine(StorageMetadataAbsDir,TEXT("AllLoadedPackageSet.json"))); + } + if(AdditionalPackageSet.Num()) + { + FFileHelper::SaveStringToFile(SerializePackageSetToString(AdditionalPackageSet),*FPaths::Combine(StorageMetadataAbsDir,TEXT("AdditionalPackageSet.json"))); + } + if(LoadedOtherCookerAssets.Num()) + { + FFileHelper::SaveStringToFile(SerializePackageSetToString(LoadedOtherCookerAssets),*FPaths::Combine(StorageMetadataAbsDir,TEXT("OtherCookerAssetPackageSet.json"))); + } + } + BulkDataManifest(); + IoStoreManifest(); + bAlready = false; + Super::Shutdown(); +} + + +void USingleCookerProxy::BulkDataManifest() +{ + SCOPED_NAMED_EVENT_TEXT("BulkDataManifest",FColor::Red); + // save bulk data manifest + for(auto& Platform:GetSettingObject()->CookTargetPlatforms) + { + if(GetSettingObject()->IoStoreSettings.bStorageBulkDataInfo) + { +#if WITH_PACKAGE_CONTEXT + UFlibHotPatcherCoreHelper::SavePlatformBulkDataManifest(GetPlatformSavePackageContexts(),Platform); +#endif + } + } +} + +void USingleCookerProxy::IoStoreManifest() +{ + SCOPED_NAMED_EVENT_TEXT("IoStoreManifest",FColor::Red); + if(GetSettingObject()->IoStoreSettings.bIoStore) + { + TSet Platforms; + for(auto Platform:GlobalCluser.Platforms) + { + Platforms.Add(Platform); + } + + for(auto Platform:Platforms) + { + FString PlatformName = THotPatcherTemplateHelper::GetEnumNameByValue(Platform); + TimeRecorder StorageCookOpenOrderTR(FString::Printf(TEXT("Storage CookOpenOrder.txt for %s, time:"),*PlatformName)); + struct CookOpenOrder + { + CookOpenOrder()=default; + CookOpenOrder(const FString& InPath,int32 InOrder):uasset_relative_path(InPath),order(InOrder){} + FString uasset_relative_path; + int32 order; + }; + auto MakeCookOpenOrder = [](const TArray& Assets)->TArray + { + TArray result; + TArray AssetsData; + TArray AssetPackagePaths; + for (auto AssetPackagePath : Assets) + { + FSoftObjectPath ObjectPath{ AssetPackagePath.ToString() }; + TArray AssetData; + UFlibAssetManageHelper::GetSpecifyAssetData(ObjectPath.GetLongPackageName(),AssetData,true); + AssetsData.Append(AssetData); + } + + // UFlibAssetManageHelper::GetAssetsData(AssetPackagePaths,AssetsData); + + for(int32 index=0;indexContainsMap() ? &FPackageName::GetMapPackageExtension() : &FPackageName::GetAssetPackageExtension(); + FPackageName::TryConvertLongPackageNameToFilename(AssetsData[index].PackageName.ToString(), LocalPath, *PackageExtension); + result.Emplace(LocalPath,index); + } + return result; + }; + TArray CookOpenOrders = MakeCookOpenOrder(GetPlatformCookAssetOrders(Platform)); + + auto SaveCookOpenOrder = [](const TArray& CookOpenOrders,const FString& File) + { + TArray result; + for(const auto& OrderFile:CookOpenOrders) + { + result.Emplace(FString::Printf(TEXT("\"%s\" %d"),*OrderFile.uasset_relative_path,OrderFile.order)); + } + FFileHelper::SaveStringArrayToFile(result,*FPaths::ConvertRelativePathToFull(File)); + }; + SaveCookOpenOrder(CookOpenOrders,FPaths::Combine(GetSettingObject()->GetStorageMetadataAbsDir(),GetSettingObject()->MissionName,PlatformName,TEXT("CookOpenOrder.txt"))); + } + } +} + +void USingleCookerProxy::InitShaderLibConllections() +{ + SCOPED_NAMED_EVENT_TEXT("USingleCookerProxy::InitShaderLibConllections",FColor::Red); + FString SavePath = GetSettingObject()->GetStorageMetadataAbsDir(); + PlatformCookShaderCollection = UFlibHotCookerHelper::CreateCookShaderCollectionProxyByPlatform( + GetSettingObject()->ShaderLibName, + GetSettingObject()->CookTargetPlatforms, + GetSettingObject()->ShaderOptions.bSharedShaderLibrary, + GetSettingObject()->ShaderOptions.bNativeShader, + true, + SavePath, + true + ); + + if(PlatformCookShaderCollection.IsValid()) + { + PlatformCookShaderCollection->Init(); + } +} + +void USingleCookerProxy::ShutdowShaderLibCollections() +{ + SCOPED_NAMED_EVENT_TEXT("USingleCookerProxy::ShutdowShaderLibCollections",FColor::Red); + if(GetSettingObject()->ShaderOptions.bSharedShaderLibrary) + { + if(PlatformCookShaderCollection.IsValid()) + { + PlatformCookShaderCollection->Shutdown(); + } + } +} + +FCookCluster USingleCookerProxy::GetPackageTrackerAsCluster() +{ + SCOPED_NAMED_EVENT_TEXT("GetPackageTrackerAsCluster",FColor::Red); + + FCookCluster PackageTrackerCluster; + + PackageTrackerCluster.Platforms = GetSettingObject()->CookTargetPlatforms; + PackageTrackerCluster.bPreGeneratePlatformData = GetSettingObject()->bPreGeneratePlatformData; + + PackageTrackerCluster.CookActionCallback.OnAssetCooked = GetOnPackageSavedCallback(); + PackageTrackerCluster.CookActionCallback.OnCookBegin = GetOnCookAssetBeginCallback(); + + if(PackageTracker && GetSettingObject()->bCookPackageTrackerAssets) + { + PackageTrackerCluster.AssetDetails.Empty(); + for(FName LongPackageName:PackageTracker->GetPendingPackageSet()) + { + if(!FPackageName::DoesPackageExist(LongPackageName.ToString())) + { + continue; + } + + // make asset data to asset registry + FString PackagePath = UFlibAssetManageHelper::LongPackageNameToPackagePath(LongPackageName.ToString()); + + FSoftObjectPath ObjectPath(PackagePath); + UFlibAssetManageHelper::UpdateAssetRegistryData(ObjectPath.GetLongPackageName()); + + FAssetData AssetData; + if(UAssetManager::Get().GetAssetDataForPath(ObjectPath,AssetData)) + { + FAssetDetail AssetDetail; + if(UFlibAssetManageHelper::ConvFAssetDataToFAssetDetail(AssetData,AssetDetail)) + { + PackageTrackerCluster.AssetDetails.Emplace(AssetDetail); + } + } + else + { + UE_LOG(LogHotPatcher,Warning,TEXT("[GetPackageTrackerAsCluster] Get %s AssetData Failed!"),*LongPackageName.ToString()); + } + } + } + return PackageTrackerCluster; +}; + +FCookActionResultEvent USingleCookerProxy::GetOnPackageSavedCallback() +{ + SCOPED_NAMED_EVENT_TEXT("GetOnPackageSavedCallback",FColor::Red); + const FCookActionResultEvent PackageSavedCallback = [this](const FSoftObjectPath& PackagePath,ETargetPlatform Platform,ESavePackageResult Result) + { + OnAssetCooked.Broadcast(PackagePath,Platform,Result); + }; + return PackageSavedCallback; +} + +FCookActionEvent USingleCookerProxy::GetOnCookAssetBeginCallback() +{ + const FCookActionEvent CookAssetBegin = [this](const FSoftObjectPath& PackagePath,ETargetPlatform Platform) + { + OnCookAssetBegin.Broadcast(PackagePath,Platform); + }; + return CookAssetBegin; +} + +TSet USingleCookerProxy::GetCookerAssets() +{ + static bool bInited = false; + static TSet PackagePaths; + if(!bInited) + { + for(const auto& Asset:GetSettingObject()->CookAssets) + { + PackagePaths.Add(*UFlibAssetManageHelper::PackagePathToLongPackageName(Asset.PackagePath.ToString())); + } + bInited = true; + } + return PackagePaths; +} + +bool USingleCookerProxy::DoExport() +{ + SCOPED_NAMED_EVENT_TEXT("DoExport",FColor::Red); + GlobalCluser.AssetDetails = GetSettingObject()->CookAssets;; + GlobalCluser.Platforms = GetSettingObject()->CookTargetPlatforms;; + GlobalCluser.CookActionCallback.OnAssetCooked = GetOnPackageSavedCallback();; + GlobalCluser.CookActionCallback.OnCookBegin = GetOnCookAssetBeginCallback(); + GlobalCluser.bPreGeneratePlatformData = GetSettingObject()->bPreGeneratePlatformData; + + UFlibAssetManageHelper::ReplaceReditector(GlobalCluser.AssetDetails); + UFlibAssetManageHelper::RemoveInvalidAssets(GlobalCluser.AssetDetails); + + ClusterCount = MakeCookQueue(GlobalCluser); + + // force cook all in onece frame + if(GetSettingObject()->bForceCookInOneFrame) + { + while(!CookCluserQueue.IsEmpty()) + { + FCookCluster CookCluster; + CookCluserQueue.Dequeue(CookCluster); + ExecCookCluster(CookCluster); + } + + if(CookCluserQueue.IsEmpty()) + { + ExecCookCluster(GetPackageTrackerAsCluster()); + } + } + + bAlready = true; + return !HasError(); +} + +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); + TArray PaddingDeleteFiles; + + // FCriticalSection LocalSynchronizationObject; + // for(const auto& Asset:ObjectPaths) + ParallelFor(ObjectPaths.Num(),[&](int32 Index) + { + FString PackageName = ObjectPaths[Index].GetLongPackageName(); + for(const auto& TargetPlatform:CookPlatfotms) + { + FString CookedSavePath = UFlibHotPatcherCoreHelper::GetAssetCookedSavePath(CookBaseDir,PackageName, TargetPlatform->PlatformName()); + // FScopeLock Lock(&LocalSynchronizationObject); + PaddingDeleteFiles.AddUnique(CookedSavePath); + } + },true); + + ParallelFor(PaddingDeleteFiles.Num(),[PaddingDeleteFiles](int32 Index) + { + FString FileName = PaddingDeleteFiles[Index]; + if(!FileName.IsEmpty() && FPaths::FileExists(FileName)) + { + IFileManager::Get().Delete(*FileName,true,true,true); + } + },GForceSingleThread); + } +} + +bool USingleCookerProxy::HasError() +{ + SCOPED_NAMED_EVENT_TEXT("USingleCookerProxy::HasError",FColor::Red); + TArray TargetPlatforms; + GetCookFailedAssetsCollection().CookFailedAssets.GetKeys(TargetPlatforms); + return !!TargetPlatforms.Num(); +} + +void USingleCookerProxy::OnAssetCookedHandle(const FSoftObjectPath& PackagePath, ETargetPlatform Platform, + ESavePackageResult Result) +{ + FScopeLock Lock(&SynchronizationObject); + SCOPED_NAMED_EVENT_TEXT("OnAssetCookedHandle",FColor::Red); + + FString PlatformName = FString::Printf(TEXT("%lld"),(int64)Platform); + if(GetPlatformNameMapping().Contains(Platform)) + { + PlatformName = GetPlatformNameMapping().Find(Platform)->ToString();; + } + + FString SavePackageResultStr = UFlibHotPatcherCoreHelper::GetSavePackageResultStr(Result); + FName AssetPathName = PackagePath.GetAssetPathName(); + FString AssetPath = PackagePath.GetAssetPathString(); + + GetPaendingCookAssetsSet().Remove(AssetPathName); + + switch(Result) + { + case ESavePackageResult::Success: + { + GetPlatformCookAssetOrders(Platform).Add(AssetPathName); + break; + } + case ESavePackageResult::Error: + case ESavePackageResult::MissingFile: + { + UE_LOG(LogHotPatcher,Error,TEXT("CookError %s for %s (%s)!"),*AssetPath,*PlatformName,*SavePackageResultStr); + GetCookFailedAssetsCollection().CookFailedAssets.FindOrAdd(Platform).PackagePaths.Add(AssetPathName); + break; + } + default: + { + UE_LOG(LogHotPatcher,Warning,TEXT("CookWarning %s for %s Failed (%s)!"),*AssetPath,*PlatformName, *SavePackageResultStr); + } + } +} + +bool USingleCookerProxy::IsFinsihed() +{ + return !GetPaendingCookAssetsSet().Num() && CookCluserQueue.IsEmpty(); +} + +#if WITH_PACKAGE_CONTEXT +TMap USingleCookerProxy::GetPlatformSavePackageContextsRaw() +{ + FScopeLock Lock(&SynchronizationObject); + SCOPED_NAMED_EVENT_TEXT("USingleCookerProxy::GetPlatformSavePackageContextsRaw",FColor::Red); + TMap result; + TArray Keys; + GetPlatformSavePackageContexts().GetKeys(Keys); + for(const auto& Key:Keys) + { + result.Add(Key,GetPlatformSavePackageContexts().Find(Key)->Get()); + } + return result; +} + +TMap USingleCookerProxy::GetPlatformSavePackageContextsNameMapping() +{ + FScopeLock Lock(&SynchronizationObject); + SCOPED_NAMED_EVENT_TEXT("USingleCookerProxy::GetPlatformSavePackageContextsNameMapping",FColor::Red); + TMap result; + TArray Keys; + GetPlatformSavePackageContexts().GetKeys(Keys); + for(const auto& Key:Keys) + { + result.Add(THotPatcherTemplateHelper::GetEnumNameByValue(Key),GetPlatformSavePackageContexts().Find(Key)->Get()); + } + return result; +} +#endif + +TArray& USingleCookerProxy::GetPlatformCookAssetOrders(ETargetPlatform Platform) +{ + FScopeLock Lock(&SynchronizationObject); + return CookAssetOrders.FindOrAdd(Platform); +} + +TSet USingleCookerProxy::GetAdditionalAssets() +{ + SCOPED_NAMED_EVENT_TEXT("GetAdditionalAssets",FColor::Red); + if(GetSettingObject()->bPackageTracker && PackageTracker.IsValid()) + { + return PackageTracker->GetPendingPackageSet(); + } + return TSet{}; +} + + +// pre cache asset type order +TArray USingleCookerProxy::GetPreCacheClasses() const +{ + return UFlibHotPatcherCoreHelper::GetPreCacheClasses(); +} + + +int32 USingleCookerProxy::GetClassAssetNumOfPerCluster(UClass* Class) +{ + int32 ClassesNumberOfAssetsPerFrame = GetSettingObject()->GetNumberOfAssetsPerFrame(); + + + for(const auto& OverrideClasses:GetSettingObject()->GetOverrideNumberOfAssetsPerFrame()) + { + if(OverrideClasses.Key == Class) + { + ClassesNumberOfAssetsPerFrame = OverrideClasses.Value; + break; + } + } + return ClassesNumberOfAssetsPerFrame; +} diff --git a/HotPatcher/Source/HotPatcherCore/Private/Cooker/PackageWriter/HotPatcherPackageWriter.cpp b/HotPatcher/Source/HotPatcherCore/Private/Cooker/PackageWriter/HotPatcherPackageWriter.cpp new file mode 100644 index 0000000..3c3d509 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Private/Cooker/PackageWriter/HotPatcherPackageWriter.cpp @@ -0,0 +1,360 @@ + +#include "Cooker/PackageWriter/HotPatcherPackageWriter.h" + +#if WITH_PACKAGE_CONTEXT && ENGINE_MAJOR_VERSION > 4 +#include "AssetRegistry/IAssetRegistry.h" +#include "Async/Async.h" +#include "Serialization/LargeMemoryWriter.h" +#include "UObject/SavePackage.h" + +void FHotPatcherPackageWriter::Initialize(const FCookInfo& Info){} + +void FHotPatcherPackageWriter::AddToExportsSize(int64& ExportsSize) +{ + TPackageWriterToSharedBuffer::AddToExportsSize(ExportsSize); +} + +void FHotPatcherPackageWriter::BeginPackage(const FBeginPackageInfo& Info) +{ + TPackageWriterToSharedBuffer::BeginPackage(Info); +} + +void FHotPatcherPackageWriter::BeginCook(){} + +void FHotPatcherPackageWriter::EndCook(){} + +// void FHotPatcherPackageWriter::Flush() +// { +// UPackage::WaitForAsyncFileWrites(); +// } + +TUniquePtr FHotPatcherPackageWriter::LoadPreviousAssetRegistry() +{ + return TUniquePtr(); +} + +FCbObject FHotPatcherPackageWriter::GetOplogAttachment(FName PackageName, FUtf8StringView AttachmentKey) +{ + return FCbObject(); +} + +void FHotPatcherPackageWriter::RemoveCookedPackages(TArrayView PackageNamesToRemove) +{ +} + +void FHotPatcherPackageWriter::RemoveCookedPackages() +{ + UPackage::WaitForAsyncFileWrites(); +} + +void FHotPatcherPackageWriter::MarkPackagesUpToDate(TArrayView UpToDatePackages) +{ +} + +bool FHotPatcherPackageWriter::GetPreviousCookedBytes(const FPackageInfo& Info, FPreviousCookedBytesData& OutData) +{ + return ICookedPackageWriter::GetPreviousCookedBytes(Info, OutData); +} + +void FHotPatcherPackageWriter::CompleteExportsArchiveForDiff(const FPackageInfo& Info, + FLargeMemoryWriter& ExportsArchive) +{ + ICookedPackageWriter::CompleteExportsArchiveForDiff(Info, ExportsArchive); +} + +void FHotPatcherPackageWriter::CollectForSavePackageData(FRecord& Record, FCommitContext& Context) +{ + Context.ExportsBuffers.AddDefaulted(Record.Packages.Num()); + for (FPackageWriterRecords::FWritePackage& Package : Record.Packages) + { + Context.ExportsBuffers[Package.Info.MultiOutputIndex].Add(FExportBuffer{ Package.Buffer, MoveTemp(Package.Regions) }); + } +} + +void FHotPatcherPackageWriter::CollectForSaveBulkData(FRecord& Record, FCommitContext& Context) +{ + for (FBulkDataRecord& BulkRecord : Record.BulkDatas) + { + if (BulkRecord.Info.BulkDataType == FBulkDataInfo::AppendToExports) + { + if (Record.bCompletedExportsArchiveForDiff) + { + // Already Added in CompleteExportsArchiveForDiff + continue; + } + Context.ExportsBuffers[BulkRecord.Info.MultiOutputIndex].Add(FExportBuffer{ BulkRecord.Buffer, MoveTemp(BulkRecord.Regions) }); + } + else + { + FWriteFileData& OutputFile = Context.OutputFiles.Emplace_GetRef(); + OutputFile.Filename = BulkRecord.Info.LooseFilePath; + OutputFile.Buffer = FCompositeBuffer(BulkRecord.Buffer); + OutputFile.Regions = MoveTemp(BulkRecord.Regions); + OutputFile.bIsSidecar = true; + OutputFile.bContributeToHash = BulkRecord.Info.MultiOutputIndex == 0; // Only caculate the main package output hash + } + } +} + +void FHotPatcherPackageWriter::CollectForSaveLinkerAdditionalDataRecords(FRecord& Record, FCommitContext& Context) +{ + if (Record.bCompletedExportsArchiveForDiff) + { + // Already Added in CompleteExportsArchiveForDiff + return; + } + + for (FLinkerAdditionalDataRecord& AdditionalRecord : Record.LinkerAdditionalDatas) + { + Context.ExportsBuffers[AdditionalRecord.Info.MultiOutputIndex].Add(FExportBuffer{ AdditionalRecord.Buffer, MoveTemp(AdditionalRecord.Regions) }); + } +} + +void FHotPatcherPackageWriter::CollectForSaveAdditionalFileRecords(FRecord& Record, FCommitContext& Context) +{ + for (FAdditionalFileRecord& AdditionalRecord : Record.AdditionalFiles) + { + FWriteFileData& OutputFile = Context.OutputFiles.Emplace_GetRef(); + OutputFile.Filename = AdditionalRecord.Info.Filename; + OutputFile.Buffer = FCompositeBuffer(AdditionalRecord.Buffer); + OutputFile.bIsSidecar = true; + OutputFile.bContributeToHash = AdditionalRecord.Info.MultiOutputIndex == 0; // Only calculate the main package output hash + } +} + +void FHotPatcherPackageWriter::CollectForSaveExportsFooter(FRecord& Record, FCommitContext& Context) +{ + if (Record.bCompletedExportsArchiveForDiff) + { + // Already Added in CompleteExportsArchiveForDiff + return; + } + + uint32 FooterData = PACKAGE_FILE_TAG; + FSharedBuffer Buffer = FSharedBuffer::Clone(&FooterData, sizeof(FooterData)); + for (FPackageWriterRecords::FWritePackage& Package : Record.Packages) + { + Context.ExportsBuffers[Package.Info.MultiOutputIndex].Add(FExportBuffer{ Buffer, TArray() }); + } +} +void FHotPatcherPackageWriter::CollectForSaveExportsBuffers(FRecord& Record, FCommitContext& Context) +{ + check(Context.ExportsBuffers.Num() == Record.Packages.Num()); + for (FPackageWriterRecords::FWritePackage& Package : Record.Packages) + { + TArray& ExportsBuffers = Context.ExportsBuffers[Package.Info.MultiOutputIndex]; + check(ExportsBuffers.Num() > 0); + + // Split the ExportsBuffer into (1) Header and (2) Exports + AllAppendedData + int64 HeaderSize = Package.Info.HeaderSize; + FExportBuffer& HeaderAndExportsBuffer = ExportsBuffers[0]; + FSharedBuffer& HeaderAndExportsData = HeaderAndExportsBuffer.Buffer; + + // Header (.uasset/.umap) + { + FWriteFileData& OutputFile = Context.OutputFiles.Emplace_GetRef(); + OutputFile.Filename = Package.Info.LooseFilePath; + OutputFile.Buffer = FCompositeBuffer( + FSharedBuffer::MakeView(HeaderAndExportsData.GetData(), HeaderSize, HeaderAndExportsData)); + OutputFile.bIsSidecar = false; + OutputFile.bContributeToHash = Package.Info.MultiOutputIndex == 0; // Only calculate the main package output hash + } + + // Exports + AllAppendedData (.uexp) + { + FWriteFileData& OutputFile = Context.OutputFiles.Emplace_GetRef(); + OutputFile.Filename = FPaths::ChangeExtension(Package.Info.LooseFilePath, LexToString(EPackageExtension::Exports)); + OutputFile.bIsSidecar = false; + OutputFile.bContributeToHash = Package.Info.MultiOutputIndex == 0; // Only caculate the main package output hash + + int32 NumBuffers = ExportsBuffers.Num(); + TArray BuffersForComposition; + BuffersForComposition.Reserve(NumBuffers); + + const uint8* ExportsStart = static_cast(HeaderAndExportsData.GetData()) + HeaderSize; + BuffersForComposition.Add(FSharedBuffer::MakeView(ExportsStart, HeaderAndExportsData.GetSize() - HeaderSize, + HeaderAndExportsData)); + OutputFile.Regions.Append(MoveTemp(HeaderAndExportsBuffer.Regions)); + + for (FExportBuffer& ExportsBuffer : TArrayView(ExportsBuffers).Slice(1, NumBuffers - 1)) + { + BuffersForComposition.Add(ExportsBuffer.Buffer); + OutputFile.Regions.Append(MoveTemp(ExportsBuffer.Regions)); + } + OutputFile.Buffer = FCompositeBuffer(BuffersForComposition); + + // Adjust regions so they are relative to the start of the uexp file + for (FFileRegion& Region : OutputFile.Regions) + { + Region.Offset -= HeaderSize; + } + } + } +} + + +TFuture FHotPatcherPackageWriter::AsyncSave(FRecord& Record, const FCommitPackageInfo& Info) +{ + FCommitContext Context{ Info }; + + // The order of these collection calls is important, both for ExportsBuffers (affects the meaning of offsets + // to those buffers) and for OutputFiles (affects the calculation of the Hash for the set of PackageData) + // The order of ExportsBuffers must match CompleteExportsArchiveForDiff. + CollectForSavePackageData(Record, Context); + CollectForSaveBulkData(Record, Context); + CollectForSaveLinkerAdditionalDataRecords(Record, Context); + CollectForSaveAdditionalFileRecords(Record, Context); + CollectForSaveExportsFooter(Record, Context); + CollectForSaveExportsBuffers(Record, Context); + + return AsyncSaveOutputFiles(Record, Context); +} + +TFuture FHotPatcherPackageWriter::AsyncSaveOutputFiles(FRecord& Record, FCommitContext& Context) +{ + if (!EnumHasAnyFlags(Context.Info.WriteOptions, EWriteOptions::Write | EWriteOptions::ComputeHash)) + { + return TFuture(); + } + + UE::SavePackageUtilities::IncrementOutstandingAsyncWrites(); + FMD5Hash OutputHash; + FMD5 AccumulatedHash; + for (FWriteFileData& OutputFile : Context.OutputFiles) + { + OutputFile.Write(AccumulatedHash, Context.Info.WriteOptions); + } + OutputHash.Set(AccumulatedHash); + + return Async(EAsyncExecution::TaskGraph,[OutputHash]()mutable ->FMD5Hash + { + UE::SavePackageUtilities::DecrementOutstandingAsyncWrites(); + return OutputHash; + }); +} + +FDateTime FHotPatcherPackageWriter::GetPreviousCookTime() const +{ + FString MetadataDirectoryPath = FPaths::ProjectDir() / TEXT("Metadata"); + const FString PreviousAssetRegistry = FPaths::Combine(MetadataDirectoryPath, GetDevelopmentAssetRegistryFilename()); + return IFileManager::Get().GetTimeStamp(*PreviousAssetRegistry); +} + +void FHotPatcherPackageWriter::CommitPackageInternal(FPackageRecord&& Record, + const IPackageWriter::FCommitPackageInfo& Info) +{ + FRecord& InRecord = static_cast(Record); + TFuture CookedHash; + if (Info.Status == ECommitStatus::Success) + { + CookedHash = AsyncSave(InRecord, Info); + } +} + +/** Version of the superclass's per-package record that includes our class-specific data. */ +struct FHotRecord : public FPackageWriterRecords::FPackage +{ +}; + +FPackageWriterRecords::FPackage* FHotPatcherPackageWriter::ConstructRecord() +{ + return new FHotRecord(); +} + +static void WriteToFile(const FString& Filename, const FCompositeBuffer& Buffer) +{ + IFileManager& FileManager = IFileManager::Get(); + + struct FFailureReason + { + uint32 LastErrorCode = 0; + bool bSizeMatchFailed = false; + }; + TOptional FailureReason; + + for (int32 Tries = 0; Tries < 3; ++Tries) + { + FArchive* Ar = FileManager.CreateFileWriter(*Filename); + if (!Ar) + { + if (!FailureReason) + { + FailureReason = FFailureReason{ FPlatformMisc::GetLastError(), false }; + } + continue; + } + + int64 DataSize = 0; + for (const FSharedBuffer& Segment : Buffer.GetSegments()) + { + int64 SegmentSize = static_cast(Segment.GetSize()); + Ar->Serialize(const_cast(Segment.GetData()), SegmentSize); + DataSize += SegmentSize; + } + delete Ar; + + if (FileManager.FileSize(*Filename) != DataSize) + { + if (!FailureReason) + { + FailureReason = FFailureReason{ 0, true }; + } + FileManager.Delete(*Filename); + continue; + } + return; + } + + TCHAR LastErrorText[1024]; + if (FailureReason && FailureReason->bSizeMatchFailed) + { + FCString::Strcpy(LastErrorText, TEXT("Unexpected file size. Another operation is modifying the file, or the write operation failed to write completely.")); + } + else if (FailureReason && FailureReason->LastErrorCode != 0) + { + FPlatformMisc::GetSystemErrorMessage(LastErrorText, UE_ARRAY_COUNT(LastErrorText), FailureReason->LastErrorCode); + } + else + { + FCString::Strcpy(LastErrorText, TEXT("Unknown failure reason.")); + } + UE_LOG(LogTemp, Fatal, TEXT("SavePackage Async write %s failed: %s"), *Filename, LastErrorText); +} + +void FHotPatcherPackageWriter::FWriteFileData::Write(FMD5& AccumulatedHash, EWriteOptions WriteOptions) const +{ + //@todo: FH: Should we calculate the hash of both output, currently only the main package output hash is calculated + if (EnumHasAnyFlags(WriteOptions, EWriteOptions::ComputeHash) && bContributeToHash) + { + for (const FSharedBuffer& Segment : Buffer.GetSegments()) + { + AccumulatedHash.Update(static_cast(Segment.GetData()), Segment.GetSize()); + } + } + + if ((bIsSidecar && EnumHasAnyFlags(WriteOptions, EWriteOptions::WriteSidecars)) || + (!bIsSidecar && EnumHasAnyFlags(WriteOptions, EWriteOptions::WritePackage))) + { + const FString* WriteFilename = &Filename; + FString FilenameBuffer; + if (EnumHasAnyFlags(WriteOptions, EWriteOptions::SaveForDiff)) + { + FilenameBuffer = FPaths::Combine(FPaths::GetPath(Filename), + FPaths::GetBaseFilename(Filename) + TEXT("_ForDiff") + FPaths::GetExtension(Filename, true)); + WriteFilename = &FilenameBuffer; + } + WriteToFile(*WriteFilename, Buffer); + + if (Regions.Num() > 0) + { + TArray Memory; + FMemoryWriter Ar(Memory); + FFileRegion::SerializeFileRegions(Ar, const_cast&>(Regions)); + + WriteToFile(*WriteFilename + FFileRegion::RegionsFileExtension, + FCompositeBuffer(FSharedBuffer::MakeView(Memory.GetData(), Memory.Num()))); + } + } +} + +#endif \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Private/Cooker/PackageWriter/HotPatcherPackageWriter.h b/HotPatcher/Source/HotPatcherCore/Private/Cooker/PackageWriter/HotPatcherPackageWriter.h new file mode 100644 index 0000000..0b28a8c --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Private/Cooker/PackageWriter/HotPatcherPackageWriter.h @@ -0,0 +1,99 @@ +#pragma once +#include "CoreMinimal.h" +#include "Resources/Version.h" + +#if WITH_PACKAGE_CONTEXT && ENGINE_MAJOR_VERSION > 4 +#include "Serialization/PackageWriter.h" +#include "PackageWriterToSharedBuffer.h" + +class FHotPatcherPackageWriter:public TPackageWriterToSharedBuffer +{ +public: + virtual FCookCapabilities GetCookCapabilities() const override + { + FCookCapabilities Result; + Result.bDiffModeSupported = true; + return Result; + } + + virtual void BeginPackage(const FBeginPackageInfo& Info) override; + virtual void AddToExportsSize(int64& ExportsSize) override; + virtual FDateTime GetPreviousCookTime() const override; + virtual void Initialize(const FCookInfo& Info) override; + virtual void BeginCook() override; + virtual void EndCook() override; + // virtual void Flush() override; + virtual TUniquePtr LoadPreviousAssetRegistry()override; + + virtual FCbObject GetOplogAttachment(FName PackageName, FUtf8StringView AttachmentKey) override; + virtual void RemoveCookedPackages(TArrayView PackageNamesToRemove) override; + virtual void RemoveCookedPackages() override; + virtual void MarkPackagesUpToDate(TArrayView UpToDatePackages) override; + virtual bool GetPreviousCookedBytes(const FPackageInfo& Info, FPreviousCookedBytesData& OutData) override; + virtual void CompleteExportsArchiveForDiff(const FPackageInfo& Info, FLargeMemoryWriter& ExportsArchive) override; + + virtual void CommitPackageInternal(FPackageRecord&& Record,const IPackageWriter::FCommitPackageInfo& Info)override; + + virtual FPackageWriterRecords::FPackage* ConstructRecord() override; + + // virtual TFuture CommitPackage(FCommitPackageInfo&& Info)override; + // virtual void WritePackageData(const FPackageInfo& Info, FLargeMemoryWriter& ExportsArchive, const TArray& FileRegions) override; + // virtual void WriteBulkData(const FBulkDataInfo& Info, const FIoBuffer& BulkData, const TArray& FileRegions) override; + // virtual void WriteLinkerAdditionalData(const FLinkerAdditionalDataInfo& Info, const FIoBuffer& Data, const TArray& FileRegions) override; + +private: + /** Version of the superclass's per-package record that includes our class-specific data. */ + struct FRecord : public FPackageWriterRecords::FPackage + { + bool bCompletedExportsArchiveForDiff = false; + }; + + /** Buffers that are combined into the HeaderAndExports file (which is then split into .uasset + .uexp or .uoasset + .uoexp). */ + struct FExportBuffer + { + FSharedBuffer Buffer; + TArray Regions; + }; + + /** + * The data needed to asynchronously write one of the files (.uasset, .uexp, .ubulk, any optional and any additional), + * without reference back to other data on this writer. + */ + struct FWriteFileData + { + FString Filename; + FCompositeBuffer Buffer; + TArray Regions; + bool bIsSidecar; + bool bContributeToHash = true; + + void Write(FMD5& AccumulatedHash, EWriteOptions WriteOptions) const; + }; + + /** Stack data for the helper functions of CommitPackageInternal. */ + struct FCommitContext + { + const FCommitPackageInfo& Info; + TArray> ExportsBuffers; + TArray OutputFiles; + }; + + void CollectForSavePackageData(FRecord& Record, FCommitContext& Context); + void CollectForSaveBulkData(FRecord& Record, FCommitContext& Context); + void CollectForSaveLinkerAdditionalDataRecords(FRecord& Record, FCommitContext& Context); + void CollectForSaveAdditionalFileRecords(FRecord& Record, FCommitContext& Context); + void CollectForSaveExportsFooter(FRecord& Record, FCommitContext& Context); + void CollectForSaveExportsBuffers(FRecord& Record, FCommitContext& Context); + + TMap>& GetPackageHashes() override + { + return AllPackageHashes; + } + // If EWriteOptions::ComputeHash is not set, the package will not get added to this. + TMap> AllPackageHashes; + + TFuture AsyncSave(FRecord& Record, const FCommitPackageInfo& Info); + TFuture AsyncSaveOutputFiles(FRecord& Record, FCommitContext& Context); +}; + +#endif \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Private/Cooker/PackageWriter/HotWorldPartitionCookPackageSplitter.cpp b/HotPatcher/Source/HotPatcherCore/Private/Cooker/PackageWriter/HotWorldPartitionCookPackageSplitter.cpp new file mode 100644 index 0000000..f0a8baf --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Private/Cooker/PackageWriter/HotWorldPartitionCookPackageSplitter.cpp @@ -0,0 +1,207 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "HotWorldPartitionCookPackageSplitter.h" + +#if WITH_EDITOR && ENGINE_MAJOR_VERSION > 4 + +#include "Misc/ConfigCacheIni.h" +#include "WorldPartition/WorldPartitionRuntimeLevelStreamingCell.h" +#include "WorldPartition/WorldPartitionLevelStreamingDynamic.h" +#include "WorldPartition/WorldPartitionRuntimeCell.h" +#include "WorldPartition/WorldPartitionRuntimeHash.h" +#include "WorldPartition/WorldPartitionSubsystem.h" +#include "WorldPartition/WorldPartition.h" +#include "WorldPartition/WorldPartitionLog.h" +#include "Editor.h" + +// Register FHotWorldPartitionCookPackageSplitter for UWorld class +// REGISTER_COOKPACKAGE_SPLITTER(FHotWorldPartitionCookPackageSplitter, UWorld); + +bool FHotWorldPartitionCookPackageSplitter::ShouldSplit(UObject* SplitData) +{ + UWorld* World = Cast(SplitData); + return World && World->IsPartitionedWorld(); +} + +FHotWorldPartitionCookPackageSplitter::FHotWorldPartitionCookPackageSplitter() +{ +} + +FHotWorldPartitionCookPackageSplitter::~FHotWorldPartitionCookPackageSplitter() +{ + check(!ReferencedWorld); +} + +void FHotWorldPartitionCookPackageSplitter::Teardown(ETeardown Status) +{ + if (bInitializedWorldPartition) + { + if (UWorld* LocalWorld = ReferencedWorld.Get()) + { + UWorldPartition* WorldPartition = LocalWorld->PersistentLevel->GetWorldPartition(); + if (WorldPartition) + { + WorldPartition->Uninitialize(); + } + } + bInitializedWorldPartition = false; + } + + if (bInitializedPhysicsSceneForSave) + { + GEditor->CleanupPhysicsSceneThatWasInitializedForSave(ReferencedWorld.Get(), bForceInitializedWorld); + bInitializedPhysicsSceneForSave = false; + bForceInitializedWorld = false; + } + + ReferencedWorld = nullptr; +} + +void FHotWorldPartitionCookPackageSplitter::AddReferencedObjects(FReferenceCollector& Collector) +{ + Collector.AddReferencedObject(ReferencedWorld); +} + +FString FHotWorldPartitionCookPackageSplitter::GetReferencerName() const +{ + return TEXT("FHotWorldPartitionCookPackageSplitter"); +} + +UWorld* FHotWorldPartitionCookPackageSplitter::ValidateDataObject(UObject* SplitData) +{ + UWorld* PartitionedWorld = CastChecked(SplitData); + check(PartitionedWorld); + check(PartitionedWorld->PersistentLevel); + check(PartitionedWorld->IsPartitionedWorld()); + return PartitionedWorld; +} + +const UWorld* FHotWorldPartitionCookPackageSplitter::ValidateDataObject(const UObject* SplitData) +{ + return ValidateDataObject(const_cast(SplitData)); +} + +TArray FHotWorldPartitionCookPackageSplitter::GetGenerateList(const UPackage* OwnerPackage, const UObject* OwnerObject) +{ + // TODO: Make WorldPartition functions const so we can honor the constness of the OwnerObject in this API function + const UWorld* ConstPartitionedWorld = ValidateDataObject(OwnerObject); + UWorld* PartitionedWorld = const_cast(ConstPartitionedWorld); + + // Store the World pointer to declare it to GarbageCollection; we do not want to allow the World to be Garbage Collected + // until we have finished all of our PreSaveGeneratedPackage calls, because we store information on the World + // that is necessary for populate + ReferencedWorld = PartitionedWorld; + + check(!bInitializedPhysicsSceneForSave && !bForceInitializedWorld); + bInitializedPhysicsSceneForSave = GEditor->InitializePhysicsSceneForSaveIfNecessary(PartitionedWorld, bForceInitializedWorld); + + // Manually initialize WorldPartition + UWorldPartition* WorldPartition = PartitionedWorld->PersistentLevel->GetWorldPartition(); + // We expect the WorldPartition has not yet been initialized + ensure(!WorldPartition->IsInitialized()); + WorldPartition->Initialize(PartitionedWorld, FTransform::Identity); + bInitializedWorldPartition = true; + + WorldPartition->BeginCook(CookContext); + + bool bIsSuccess = true; + for (IWorldPartitionCookPackageGenerator* CookPackageGenerator : CookContext.GetCookPackageGenerators()) + { + bIsSuccess &= CookPackageGenerator->GatherPackagesToCook(CookContext); + } + + UE_LOG(LogHotWorldPartition, Log, TEXT("[Cook] Gathered %u packages to generate from %u Generators."), CookContext.NumPackageToGenerate(), CookContext.NumGenerators()); + + TArray PackagesToGenerate; + BuildPackagesToGenerateList(PackagesToGenerate); + + UE_LOG(LogHotWorldPartition, Log, TEXT("[Cook] Sending %u packages to be generated."), PackagesToGenerate.Num()); + + return PackagesToGenerate; +} + +bool FHotWorldPartitionCookPackageSplitter::PopulateGeneratedPackage(UPackage* OwnerPackage, UObject* OwnerObject, + const FGeneratedPackageForPopulate& GeneratedPackage, TArray& OutObjectsToMove, + TArray& OutModifiedPackages) +{ + UE_LOG(LogHotWorldPartition, Verbose, TEXT("[Cook][PopulateGeneratedPackage] Processing %s"), *FWorldPartitionCookPackage::MakeGeneratedFullPath(GeneratedPackage.GeneratedRootPath, GeneratedPackage.RelativePath)); + + bool bIsSuccess = true; + + IWorldPartitionCookPackageGenerator* CookPackageGenerator = nullptr; + FWorldPartitionCookPackage* CookPackage = nullptr; + TArray ModifiedPackages; + if (CookContext.GetCookPackageGeneratorAndPackage(GeneratedPackage.GeneratedRootPath, GeneratedPackage.RelativePath, CookPackageGenerator, CookPackage)) + { + bIsSuccess = CookPackageGenerator->PopulateGeneratedPackageForCook(CookContext, *CookPackage, ModifiedPackages); + } + else + { + UE_LOG(LogHotWorldPartition, Error, TEXT("[Cook][PopulateGeneratedPackage] Could not find WorldPartitionCookPackage for %s"), *FWorldPartitionCookPackage::MakeGeneratedFullPath(GeneratedPackage.GeneratedRootPath, GeneratedPackage.RelativePath)); + bIsSuccess = false; + } + + UE_LOG(LogHotWorldPartition, Verbose, TEXT("[Cook][PopulateGeneratedPackage] Gathered %u modified packages for %s"), ModifiedPackages.Num() , *FWorldPartitionCookPackage::MakeGeneratedFullPath(GeneratedPackage.GeneratedRootPath, GeneratedPackage.RelativePath)); + OutModifiedPackages = MoveTemp(ModifiedPackages); + + return bIsSuccess; +} + +bool FHotWorldPartitionCookPackageSplitter::PopulateGeneratorPackage(UPackage* OwnerPackage, UObject* OwnerObject, + const TArray& GeneratedPackages, TArray& OutObjectsToMove, + TArray& OutModifiedPackages) +{ + UE_LOG(LogHotWorldPartition, Log, TEXT("[Cook][PopulateGeneratorPackage] Processing %u packages"), GeneratedPackages.Num()); + + bool bIsSuccess = true; + if (GeneratedPackages.Num() != CookContext.NumPackageToGenerate()) + { + UE_LOG(LogHotWorldPartition, Error, TEXT("[Cook][PopulateGeneratorPackage] Receieved %u generated packages. Was expecting %u"), GeneratedPackages.Num(), CookContext.NumPackageToGenerate()); + bIsSuccess = false; + } + + TArray ModifiedPackages; + for (IWorldPartitionCookPackageGenerator* CookPackageGenerator : CookContext.GetCookPackageGenerators()) + { + bIsSuccess &= CookPackageGenerator->PrepareGeneratorPackageForCook(CookContext, ModifiedPackages); + if (const TArray* CookPackages = CookContext.GetCookPackages(CookPackageGenerator)) + { + bIsSuccess &= CookPackageGenerator->PopulateGeneratorPackageForCook(CookContext, *CookPackages, ModifiedPackages); + } + } + + UE_LOG(LogHotWorldPartition, Log, TEXT("[Cook][PopulateGeneratorPackage] Gathered %u modified packages"), ModifiedPackages.Num()); + OutModifiedPackages = MoveTemp(ModifiedPackages); + + return bIsSuccess; +} + +void FHotWorldPartitionCookPackageSplitter::OnOwnerReloaded(UPackage* OwnerPackage, UObject* OwnerObject) +{ + // It should not be possible for the owner to reload due to garbage collection while we are active and keeping it referenced + check(!ReferencedWorld); +} + +void FHotWorldPartitionCookPackageSplitter::BuildPackagesToGenerateList(TArray& PackagesToGenerate) const +{ + for (const IWorldPartitionCookPackageGenerator* CookPackageGenerator : CookContext.GetCookPackageGenerators()) + { + if (const TArray* CookPackages = CookContext.GetCookPackages(CookPackageGenerator)) + { + PackagesToGenerate.Reserve(CookPackages->Num()); + + for (const FWorldPartitionCookPackage* CookPackage : *CookPackages) + { + ICookPackageSplitter::FGeneratedPackage& GeneratedPackage = PackagesToGenerate.Emplace_GetRef(); + GeneratedPackage.GeneratedRootPath = CookPackage->Root; + GeneratedPackage.RelativePath = CookPackage->RelativePath; + + CookPackage->Type == FWorldPartitionCookPackage::EType::Level ? GeneratedPackage.SetCreateAsMap(true) : GeneratedPackage.SetCreateAsMap(false); + + // @todo_ow: Set dependencies once we get iterative cooking working + } + } + } +} + +#endif diff --git a/HotPatcher/Source/HotPatcherCore/Private/Cooker/PackageWriter/HotWorldPartitionCookPackageSplitter.h b/HotPatcher/Source/HotPatcherCore/Private/Cooker/PackageWriter/HotWorldPartitionCookPackageSplitter.h new file mode 100644 index 0000000..119d3d3 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Private/Cooker/PackageWriter/HotWorldPartitionCookPackageSplitter.h @@ -0,0 +1,53 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "Resources/Version.h" + +#if WITH_EDITOR && ENGINE_MAJOR_VERSION > 4 + +#include "CookPackageSplitter.h" +#include "Engine/World.h" +#include "UObject/WeakObjectPtrTemplates.h" +#include "WorldPartition/Cook/WorldPartitionCookPackageContext.h" + +class FHotWorldPartitionCookPackageSplitter : public FGCObject, public ICookPackageSplitter +{ +public: + //~ Begin of ICookPackageSplitter + static bool ShouldSplit(UObject* SplitData); + + FHotWorldPartitionCookPackageSplitter(); + virtual ~FHotWorldPartitionCookPackageSplitter(); + + virtual void Teardown(ETeardown Status) override; + virtual bool UseInternalReferenceToAvoidGarbageCollect() override { return true; } + virtual TArray GetGenerateList(const UPackage* OwnerPackage, const UObject* OwnerObject) override; + virtual bool PopulateGeneratedPackage(UPackage* OwnerPackage, UObject* OwnerObject, + const FGeneratedPackageForPopulate& GeneratedPackage, TArray& OutObjectsToMove, TArray& OutModifiedPackages) override; + virtual bool PopulateGeneratorPackage(UPackage* OwnerPackage, UObject* OwnerObject, + const TArray& GeneratedPackages, TArray& OutObjectsToMove, + TArray& OutModifiedPackages) override; + virtual void OnOwnerReloaded(UPackage* OwnerPackage, UObject* OwnerObject) override; + //~ End of ICookPackageSplitter + +private: + /** FGCObject interface */ + virtual void AddReferencedObjects(FReferenceCollector& Collector) override; + virtual FString GetReferencerName() const override; + + const UWorld* ValidateDataObject(const UObject* SplitData); + UWorld* ValidateDataObject(UObject* SplitData); + + void BuildPackagesToGenerateList(TArray& PackagesToGenerate) const; + bool MapGeneratePackageToCookPackage(const TArray& GeneratedPackages); + + TObjectPtr ReferencedWorld = nullptr; + + FWorldPartitionCookPackageContext CookContext; + + bool bInitializedWorldPartition = false; + bool bForceInitializedWorld = false; + bool bInitializedPhysicsSceneForSave = false; +}; + +#endif \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Private/Cooker/PackageWriter/WorldPartition/Cook/WorldPartitionCookPackageContext.cpp b/HotPatcher/Source/HotPatcherCore/Private/Cooker/PackageWriter/WorldPartition/Cook/WorldPartitionCookPackageContext.cpp new file mode 100644 index 0000000..dec276b --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Private/Cooker/PackageWriter/WorldPartition/Cook/WorldPartitionCookPackageContext.cpp @@ -0,0 +1,84 @@ +// Copyright Epic Games, Inc. All Rights Reserved. +#include "WorldPartitionCookPackageContext.h" + +#if WITH_EDITOR && !UE_VERSION_OLDER_THAN(5,1,0) + +#include "WorldPartition/WorldPartitionLog.h" +#include "WorldPartition/Cook/WorldPartitionCookPackageGenerator.h" + +DEFINE_LOG_CATEGORY(LogHotWorldPartition) + +FWorldPartitionCookPackageContext::FWorldPartitionCookPackageContext() +{ +} + +void FWorldPartitionCookPackageContext::RegisterPackageCookPackageGenerator(IWorldPartitionCookPackageGenerator* CookPackageGenerator) +{ + check(!CookPackageGenerators.Contains(CookPackageGenerator)); + CookPackageGenerators.Add(CookPackageGenerator); + check(!PackagesToCookByGenerator.Contains(CookPackageGenerator)); +} + +const FWorldPartitionCookPackage* FWorldPartitionCookPackageContext::AddLevelStreamingPackageToGenerate(IWorldPartitionCookPackageGenerator* CookPackageGenerator, const FString& Root, const FString& RelativePath) +{ + return AddPackageToGenerateInternal(CookPackageGenerator, Root, RelativePath, FWorldPartitionCookPackage::EType::Level); +} + +const FWorldPartitionCookPackage* FWorldPartitionCookPackageContext::AddGenericPackageToGenerate(IWorldPartitionCookPackageGenerator* CookPackageGenerator, const FString& Root, const FString& RelativePath) +{ + return AddPackageToGenerateInternal(CookPackageGenerator, Root, RelativePath, FWorldPartitionCookPackage::EType::Generic); +} + +bool FWorldPartitionCookPackageContext::GetCookPackageGeneratorAndPackage(const FString& PackageRoot, const FString& PackageRelativePath, IWorldPartitionCookPackageGenerator*& CookPackageGenerator, FWorldPartitionCookPackage*& CookPackage) +{ + FWorldPartitionCookPackage::IDType PackageId = FWorldPartitionCookPackage::MakeCookPackageID(PackageRoot, PackageRelativePath); + if (IWorldPartitionCookPackageGenerator** GeneratorPtr = CookGeneratorByPackageId.Find(PackageId)) + { + if (TUniquePtr* PackagePtr = PackagesToCookById.Find(PackageId)) + { + check((*PackagePtr)->Root.Equals(PackageRoot, ESearchCase::IgnoreCase) && (*PackagePtr)->RelativePath.Equals(PackageRelativePath, ESearchCase::IgnoreCase)); + + CookPackageGenerator = *GeneratorPtr; + CookPackage = (*PackagePtr).Get(); + return true; + } + } + + return false; +} + +const FWorldPartitionCookPackage* FWorldPartitionCookPackageContext::AddPackageToGenerateInternal(IWorldPartitionCookPackageGenerator* CookPackageGenerator, const FString& Root, const FString& RelativePath, FWorldPartitionCookPackage::EType Type) +{ + if (CookPackageGenerators.Contains(CookPackageGenerator)) + { + FWorldPartitionCookPackage::IDType PackageId = FWorldPartitionCookPackage::MakeCookPackageID(FWorldPartitionCookPackage::SanitizePathComponent(Root), FWorldPartitionCookPackage::SanitizePathComponent(RelativePath)); + TUniquePtr* ExistingPackage = PackagesToCookById.Find(PackageId); + if (ExistingPackage == nullptr) + { + TUniquePtr& CookPackage = PackagesToCookById.Emplace(PackageId, MakeUnique(FWorldPartitionCookPackage::SanitizePathComponent(Root), FWorldPartitionCookPackage::SanitizePathComponent(RelativePath), Type)); + check(PackageId == CookPackage->PackageId); + + CookGeneratorByPackageId.Add(PackageId, CookPackageGenerator); + + TArray& PackagesToCookForHandler = PackagesToCookByGenerator.FindOrAdd(CookPackageGenerator); + PackagesToCookForHandler.Add(CookPackage.Get()); + + UE_LOG(LogHotWorldPartition, Verbose, TEXT("[Cook] Added Package %s with ID %llu in context"), *CookPackage->GetFullGeneratedPath(), PackageId); + + return CookPackage.Get(); + } + else + { + UE_LOG(LogHotWorldPartition, Error, TEXT("[Cook] Trying to add package %s in context but there is already a package to generate with the same ID (%llu). Other package: %s Id %llu"), + *FWorldPartitionCookPackage::MakeGeneratedFullPath(Root, RelativePath), PackageId, *(*ExistingPackage)->GetFullGeneratedPath(), (*ExistingPackage)->PackageId); + } + } + else + { + UE_LOG(LogHotWorldPartition, Error, TEXT("[Cook] Trying to add package %s in context, but its generator is not registered."), *FWorldPartitionCookPackage::MakeGeneratedFullPath(Root, RelativePath)); + } + + return nullptr; +} + +#endif \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Private/Cooker/PackageWriter/WorldPartition/Cook/WorldPartitionCookPackageContext.h b/HotPatcher/Source/HotPatcherCore/Private/Cooker/PackageWriter/WorldPartition/Cook/WorldPartitionCookPackageContext.h new file mode 100644 index 0000000..2eac3ae --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Private/Cooker/PackageWriter/WorldPartition/Cook/WorldPartitionCookPackageContext.h @@ -0,0 +1,48 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "CoreMinimal.h" +#include "Misc/EngineVersionComparison.h" + +#if WITH_EDITOR && !UE_VERSION_OLDER_THAN(5,1,0) + +#include "WorldPartition/Cook/WorldPartitionCookPackageContextInterface.h" +#include "WorldPartition/Cook/WorldPartitionCookPackage.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogHotWorldPartition,All,All); + +class IWorldPartitionCookPackageGenerator; + +class FWorldPartitionCookPackageContext : public IWorldPartitionCookPackageContext +{ +public: + FWorldPartitionCookPackageContext(); + + //~ Begin IWorldPartitionCookPackageContext Interface + virtual void RegisterPackageCookPackageGenerator(IWorldPartitionCookPackageGenerator* CookPackageGenerator) override; + virtual void UnregisterPackageCookPackageGenerator(IWorldPartitionCookPackageGenerator* CookPackageGenerator) override { check(0); /*No use case*/ } + + virtual const FWorldPartitionCookPackage* AddLevelStreamingPackageToGenerate(IWorldPartitionCookPackageGenerator* CookPackageGenerator, const FString& Root, const FString& RelativePath) override; + virtual const FWorldPartitionCookPackage* AddGenericPackageToGenerate(IWorldPartitionCookPackageGenerator* CookPackageGenerator, const FString& Root, const FString& RelativePath) override; + //~ End IWorldPartitionCookPackageContext Interface + + const TArray* GetCookPackages(const IWorldPartitionCookPackageGenerator* CookPackageGenerator) const { return PackagesToCookByGenerator.Find(CookPackageGenerator); } + + bool GetCookPackageGeneratorAndPackage(const FString& PackageRoot, const FString& PackageRelativePath, IWorldPartitionCookPackageGenerator*& CookPackageGenerator, FWorldPartitionCookPackage*& CookPackage); + + uint32 NumPackageToGenerate() const { return PackagesToCookById.Num(); } + uint32 NumGenerators() const { return CookPackageGenerators.Num(); } + + TArray& GetCookPackageGenerators() { return CookPackageGenerators; } + const TArray& GetCookPackageGenerators() const { return CookPackageGenerators; } + +private: + const FWorldPartitionCookPackage* AddPackageToGenerateInternal(IWorldPartitionCookPackageGenerator* CookPackageGenerator, const FString& Root, const FString& RelativePath, FWorldPartitionCookPackage::EType Type); + + TArray CookPackageGenerators; + TMap> PackagesToCookById; + TMap CookGeneratorByPackageId; + TMap> PackagesToCookByGenerator; +}; + +#endif \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Private/CreatePatch/PatcherProxy.cpp b/HotPatcher/Source/HotPatcherCore/Private/CreatePatch/PatcherProxy.cpp new file mode 100644 index 0000000..8d147cc --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Private/CreatePatch/PatcherProxy.cpp @@ -0,0 +1,1589 @@ +// project header +#include "CreatePatch/PatcherProxy.h" +#include "HotPatcherLog.h" +#include "ThreadUtils/FThreadUtils.hpp" +#include "CreatePatch/HotPatcherContext.h" +#include "CreatePatch/ScopedSlowTaskContext.h" +#include "CreatePatch/HotPatcherContext.h" + +#include "FlibHotPatcherCoreHelper.h" +#include "ShaderLibUtils/FlibShaderCodeLibraryHelper.h" +#include "Cooker/MultiCooker/FCookShaderCollectionProxy.h" + +// engine header +#include "Async/Async.h" +#include "CoreGlobals.h" +#include "AssetRegistryState.h" +#include "ShaderCompiler.h" +#include "Dom/JsonValue.h" +#include "HAL/PlatformFilemanager.h" +#include "Kismet/KismetStringLibrary.h" +#include "PakFileUtilities.h" +#include "Kismet/KismetSystemLibrary.h" +#include "Misc/FileHelper.h" +#include "Templates/Function.h" +#include "BinariesPatchFeature.h" +#include "HotPatcherCore.h" +#include "HotPatcherDelegates.h" +#include "HotPatcherSettings.h" +#include "Features/IModularFeatures.h" +#include "Async/ParallelFor.h" +#include "Cooker/MultiCooker/FlibHotCookerHelper.h" +#include "Cooker/MultiCooker/FSingleCookerSettings.h" +#include "Cooker/MultiCooker/SingleCookerProxy.h" +#include "DependenciesParser/FDefaultAssetDependenciesParser.h" +#include "Misc/DataDrivenPlatformInfoRegistry.h" +#include "Serialization/ArrayWriter.h" +#include "ThreadUtils/FProcWorkerThread.hpp" + +#if WITH_IO_STORE_SUPPORT +#include "IoStoreUtilities.h" +#endif + +#define LOCTEXT_NAMESPACE "HotPatcherProxy" + +UPatcherProxy::UPatcherProxy(const FObjectInitializer& ObjectInitializer):Super(ObjectInitializer){} + +bool UPatcherProxy::CanExportPatch()const +{ + return UFlibPatchParserHelper::IsValidPatchSettings(const_cast(this)->GetSettingObject(),false); +} + +FString GetShaderLibDeterministicCmdByPlatforms(const TArray& PlatformNames) +{ + FString ShaderLibDeterministicCommand; + if(!!PlatformNames.Num()) + { + ShaderLibDeterministicCommand = TEXT("TARGETPLATFORM="); + for(const auto& PlatfromName:PlatformNames) + { + ShaderLibDeterministicCommand += FString::Printf(TEXT("%s+"),*PlatfromName); + } + ShaderLibDeterministicCommand.RemoveFromEnd(TEXT("+")); + } + return ShaderLibDeterministicCommand; +} + +namespace PatchWorker +{ + // setup 1 + bool BaseVersionReader(FHotPatcherPatchContext& Context); + // setup 2 + bool MakeCurrentVersionWorker(FHotPatcherPatchContext& Context); + // setup 3 + bool ParseVersionDiffWorker(FHotPatcherPatchContext& Context); + // setup 3.1 + bool ParseDiffAssetOnlyWorker(FHotPatcherPatchContext& Context); + // setup 4 + bool PatchRequireChekerWorker(FHotPatcherPatchContext& Context); + // setup 5 + bool SavePakVersionWorker(FHotPatcherPatchContext& Context); + // setup 6 + bool ParserChunkWorker(FHotPatcherPatchContext& Context); + + bool PreCookPatchAssets(FHotPatcherPatchContext& Context); + // setup 7 + bool CookPatchAssetsWorker(FHotPatcherPatchContext& Context); + // setup 7.1 + bool PostCookPatchAssets(FHotPatcherPatchContext& Context); + + // setup 7.1 + bool PatchAssetRegistryWorker(FHotPatcherPatchContext& Context); + // setup 7.2 + bool GenerateGlobalAssetRegistryManifest(FHotPatcherPatchContext& Context); + // setup 8 + bool GeneratePakProxysWorker(FHotPatcherPatchContext& Context); + // setup 9 + bool CreatePakWorker(FHotPatcherPatchContext& Context); + // io store + bool CreateIoStoreWorker(FHotPatcherPatchContext& Context); + + bool SaveDifferenceWorker(FHotPatcherPatchContext& Context); + // setup 12 + bool SaveNewReleaseWorker(FHotPatcherPatchContext& Context); + // setup 13 serialize all pak file info + bool SavePakFileInfoWorker(FHotPatcherPatchContext& Context); + // setup 14 serialize patch config + bool SavePatchConfigWorker(FHotPatcherPatchContext& Context); + // setup 15 + // bool BackupMetadataWorker(FHotPatcherPatchContext& Context); + // setup 16 + bool ShowSummaryWorker(FHotPatcherPatchContext& Context); + // setup 17 + bool OnFaildDispatchWorker(FHotPatcherPatchContext& Context); + // setup 18 + bool NotifyOperatorsWorker(FHotPatcherPatchContext& Context); + + void GenerateBinariesPatch(FHotPatcherPatchContext& Context,FChunkInfo& Chunk,ETargetPlatform Platform,TArray& PakCommands); + + struct FTrackPackageAction + { + FTrackPackageAction(FHotPatcherPatchContext& InContext,const FChunkInfo& InChunkInfo,const TArray& InPlatforms):Context(InContext),ChunkInfo(InChunkInfo),Platforms(InPlatforms) + { + if(Context.GetSettingObject()->IsPackageTracker()) + { + PackageTrackerByDiff = MakeShareable(new FPackageTrackerByDiff(Context)); + } + } + ~FTrackPackageAction() + { + if(Context.GetSettingObject()->IsPackageTracker() && PackageTrackerByDiff.IsValid()) + { + TArray IgnoreFilters = UFlibAssetManageHelper::DirectoriesToStrings(Context.GetSettingObject()->GetAssetIgnoreFilters()); + TArray ForceSkipFilters = UFlibAssetManageHelper::DirectoriesToStrings(Context.GetSettingObject()->GetForceSkipContentRules()); + TArray ForceSkipAssets = UFlibAssetManageHelper::SoftObjectPathsToStrings(Context.GetSettingObject()->GetForceSkipAssets()); + TSet ForceSkipTypes = UFlibAssetManageHelper::GetClassesNames(Context.GetSettingObject()->GetForceSkipClasses()); + + TArray TrackerAssetDetails; + for(const auto& TrackPackage:PackageTrackerByDiff->GetTrackResult()) + { + bool bSkiped = FAssetDependenciesParser::IsForceSkipAsset(TrackPackage.Key.ToString(),ForceSkipTypes,IgnoreFilters,ForceSkipFilters,ForceSkipAssets,true); + bool bContainInBaseVersion = FTrackPackageAction::IsAssetContainIn(Context.BaseVersion.AssetInfo,TrackPackage.Value); + if(!bSkiped && !bContainInBaseVersion) + { + TrackerAssetDetails.AddUnique(TrackPackage.Value); + Context.AddAsset(ChunkInfo.ChunkName,TrackPackage.Value); + } + } + } + } + static bool IsAssetContainIn(const FAssetDependenciesInfo& InAssetInfo,const FAssetDetail& InAssetDetail) + { + bool bContainInBaseVersion = false; + FSoftObjectPath SoftObjectPath{InAssetDetail.PackagePath}; + FAssetDetail BaseAssetDetail; + bool bHasInBase = InAssetInfo.GetAssetDetailByPackageName(SoftObjectPath.GetLongPackageName(),BaseAssetDetail); + if(bHasInBase) + { + bContainInBaseVersion = (BaseAssetDetail.Guid == InAssetDetail.Guid); + } + return bContainInBaseVersion; + } + private: + FHotPatcherPatchContext& Context; + const FChunkInfo& ChunkInfo; + TArray Platforms; + TSharedPtr PackageTrackerByDiff; + }; + + template + void ExportStructToFile(FHotPatcherPatchContext& Context,T& Settings,const FString& SaveTo,bool bNotify,const FString& NotifyName) + { + FString SerializedJsonStr; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(Settings,SerializedJsonStr); + if (FFileHelper::SaveStringToFile(SerializedJsonStr, *SaveTo)) + { + if(::IsRunningCommandlet()) + { + FString Msg = FString::Printf(TEXT("Export %s to %s."),*NotifyName,*SaveTo); + Context.OnPaking.Broadcast(TEXT("SavedPatchConfig"),Msg); + }else if(bNotify) + { + FString MsgStr = FString::Printf(TEXT("Export %s to Successfuly."),*NotifyName); + FText Msg = UKismetTextLibrary::Conv_StringToText(MsgStr); + FHotPatcherDelegates::Get().GetNotifyFileGenerated().Broadcast(Msg, SaveTo); + } + } + }; +} + +#define ADD_PATCH_WORKER(FUNC_NAME) AddPatchWorker(ANSI_TO_TCHAR(#FUNC_NAME),&FUNC_NAME); + +void UPatcherProxy::Init(FPatcherEntitySettingBase* InSetting) +{ + SCOPED_NAMED_EVENT_TEXT("UPatcherProxy::Init",FColor::Red); + Super::Init(InSetting); +#if WITH_PACKAGE_CONTEXT + PlatformSavePackageContexts = UFlibHotPatcherCoreHelper::CreatePlatformsPackageContexts( + GetSettingObject()->GetPakTargetPlatforms(), + GetSettingObject()->IoStoreSettings.bIoStore, + GetSettingObject()->GetStorageCookedDir() + ); +#endif + UFlibAssetManageHelper::UpdateAssetMangerDatabase(true); + GetSettingObject()->Init(); + ADD_PATCH_WORKER(PatchWorker::BaseVersionReader); + ADD_PATCH_WORKER(PatchWorker::MakeCurrentVersionWorker); + ADD_PATCH_WORKER(PatchWorker::SavePatchConfigWorker); + ADD_PATCH_WORKER(PatchWorker::ParseVersionDiffWorker); + ADD_PATCH_WORKER(PatchWorker::ParseDiffAssetOnlyWorker); + ADD_PATCH_WORKER(PatchWorker::PatchRequireChekerWorker); + ADD_PATCH_WORKER(PatchWorker::SavePakVersionWorker); + ADD_PATCH_WORKER(PatchWorker::ParserChunkWorker); + ADD_PATCH_WORKER(PatchWorker::PreCookPatchAssets); + ADD_PATCH_WORKER(PatchWorker::CookPatchAssetsWorker); + // cook finished + ADD_PATCH_WORKER(PatchWorker::PostCookPatchAssets); + ADD_PATCH_WORKER(PatchWorker::PatchAssetRegistryWorker); + ADD_PATCH_WORKER(PatchWorker::GenerateGlobalAssetRegistryManifest); + ADD_PATCH_WORKER(PatchWorker::GeneratePakProxysWorker); + ADD_PATCH_WORKER(PatchWorker::CreatePakWorker); + ADD_PATCH_WORKER(PatchWorker::CreateIoStoreWorker); + ADD_PATCH_WORKER(PatchWorker::SaveDifferenceWorker); + ADD_PATCH_WORKER(PatchWorker::SaveNewReleaseWorker); + ADD_PATCH_WORKER(PatchWorker::SavePakFileInfoWorker); + // ADD_PATCH_WORKER(PatchWorker::BackupMetadataWorker); + ADD_PATCH_WORKER(PatchWorker::ShowSummaryWorker); + ADD_PATCH_WORKER(PatchWorker::OnFaildDispatchWorker); +} + +void UPatcherProxy::Shutdown() +{ + Super::Shutdown(); +} + +bool UPatcherProxy::DoExport() +{ + FScopedNamedEventStatic DoExportTag(FColor::Red,*FString::Printf(TEXT("DoExport_%s"),*GetSettingObject()->VersionId)); + PatchContext = MakeShareable(new FHotPatcherPatchContext); + PatchContext->PatchProxy = this; + PatchContext->OnPaking.AddLambda([this](const FString& One,const FString& Msg){this->OnPaking.Broadcast(One,Msg);}); + PatchContext->OnShowMsg.AddLambda([this](const FString& Msg){ this->OnShowMsg.Broadcast(Msg);}); + PatchContext->UnrealPakSlowTask = NewObject(); + PatchContext->UnrealPakSlowTask->AddToRoot(); + PatchContext->ContextSetting = GetSettingObject(); + PatchContext->Init(); + + // wait cook complete + if(PatchContext->GetSettingObject()->IsBinariesPatch()) + { + this->OnPakListGenerated.AddStatic(&PatchWorker::GenerateBinariesPatch); + } + + float AmountOfWorkProgress = (float)GetPatchWorkers().Num() + (float)(GetSettingObject()->GetPakTargetPlatforms().Num() * FMath::Max(PatchContext->PakChunks.Num(),1)); + PatchContext->UnrealPakSlowTask->init(AmountOfWorkProgress); + + bool bRet = true; + for(auto Worker:GetPatchWorkers()) + { + if(!Worker.Value(*PatchContext)) + { + bRet = false; + break; + } + } + + PatchContext->UnrealPakSlowTask->Final(); + PatchContext->Shurdown(); + return bRet; +} + + +namespace PatchWorker +{ + // setup 1 + bool BaseVersionReader(FHotPatcherPatchContext& Context) + { + SCOPED_NAMED_EVENT_TEXT("BaseVersionReader",FColor::Red); + TimeRecorder ReadBaseVersionTR(TEXT("Deserialize Base Version")); + if (Context.GetSettingObject()->IsByBaseVersion() && !Context.GetSettingObject()->GetBaseVersionInfo(Context.BaseVersion)) + { + UE_LOG(LogHotPatcher, Error, TEXT("Deserialize Base Version Faild!")); + return false; + } + else + { + // 在不进行外部文件diff的情况下清理掉基础版本的外部文件 + if (!Context.GetSettingObject()->IsEnableExternFilesDiff()) + { + Context.BaseVersion.PlatformAssets.Empty(); + } + } + return true; + }; + + // setup 2 + bool MakeCurrentVersionWorker(FHotPatcherPatchContext& Context) + { + SCOPED_NAMED_EVENT_TEXT("MakeCurrentVersionWorker",FColor::Red); + UE_LOG(LogHotPatcher,Display,TEXT("Make Patch Setting...")); + TimeRecorder ExportNewVersionTR(TEXT("Make Release Chunk/Export Release Version Info By Chunk")); + + // import cook dir and uasset / not-uasset directories to settings + if(Context.GetSettingObject()->IsImportProjectSettings()) + { + UFlibHotPatcherCoreHelper::ImportProjectSettingsToScannerConfig(Context.GetSettingObject()->GetAssetScanConfigRef()); + + ETargetPlatform AddProjectDirToTarget = ETargetPlatform::AllPlatforms; + + TSet AllConfigPlatformSet; + for(const auto& PakTargetPlatform:Context.GetSettingObject()->GetPakTargetPlatforms()) + { + AllConfigPlatformSet.Add(PakTargetPlatform); + } + + AddProjectDirToTarget = AllConfigPlatformSet.Num() > 1 ? ETargetPlatform::AllPlatforms : AllConfigPlatformSet.Array()[0]; + + UFlibHotPatcherCoreHelper::ImportProjectNotAssetDir( + Context.GetSettingObject()->GetAddExternAssetsToPlatform(), + AddProjectDirToTarget + ); + } + + Context.NewVersionChunk = UFlibHotPatcherCoreHelper::MakeChunkFromPatchSettings(Context.GetSettingObject()); + + UE_LOG(LogHotPatcher,Display,TEXT("Deserialize Release Version by Patch Setting...")); + Context.CurrentVersion = UFlibPatchParserHelper::ExportReleaseVersionInfoByChunk( + Context.GetSettingObject()->GetVersionId(), + Context.BaseVersion.VersionId, + FDateTime::UtcNow().ToString(), + Context.NewVersionChunk, + Context.GetSettingObject()->IsIncludeHasRefAssetsOnly(), + Context.GetSettingObject()->IsAnalysisFilterDependencies(), + Context.GetSettingObject()->GetHashCalculator() + ); + return true; + }; + + // setup 3 + bool ParseVersionDiffWorker(FHotPatcherPatchContext& Context) + { + SCOPED_NAMED_EVENT_TEXT("ParseVersionDiffWorker",FColor::Red); + TimeRecorder DiffVersionTR(TEXT("Diff Base Version And Current Project Version")); + Context.VersionDiff = UFlibHotPatcherCoreHelper::DiffPatchVersionWithPatchSetting(*Context.GetSettingObject(), Context.BaseVersion, Context.CurrentVersion); + return true; + }; + + bool ParseDiffAssetOnlyWorker(FHotPatcherPatchContext& Context) + { + SCOPED_NAMED_EVENT_TEXT("ParseDiffAssetOnlyWorker",FColor::Red); + TimeRecorder DiffVersionAssetOnlyTR(TEXT("Parse Diff Asset Dependencies Only Worker")); + if(Context.GetSettingObject()->IsAnalysisDiffAssetDependenciesOnly()) + { + FAssetDependenciesInfo AddAndModifyInfo = UFlibAssetManageHelper::CombineAssetDependencies(Context.VersionDiff.AssetDiffInfo.AddAssetDependInfo,Context.VersionDiff.AssetDiffInfo.ModifyAssetDependInfo); + const TArray& DiffAssetDetails = AddAndModifyInfo.GetAssetDetails(); + + FChunkInfo DiffChunk; + for(const auto& AssetDetail:DiffAssetDetails) + { + FPatcherSpecifyAsset CurrentAsets; + CurrentAsets.Asset = FSoftObjectPath(AssetDetail.PackagePath.ToString()); + CurrentAsets.bAnalysisAssetDependencies = true; + CurrentAsets.AssetRegistryDependencyTypes.AddUnique(EAssetRegistryDependencyTypeEx::Packages); + DiffChunk.IncludeSpecifyAssets.Add(CurrentAsets); + } + + FHotPatcherVersion DiffVersion = UFlibPatchParserHelper::ExportReleaseVersionInfoByChunk( + Context.GetSettingObject()->GetVersionId(), + Context.BaseVersion.VersionId, + FDateTime::UtcNow().ToString(), + DiffChunk, + Context.GetSettingObject()->IsIncludeHasRefAssetsOnly(), + Context.GetSettingObject()->IsAnalysisFilterDependencies(), + Context.GetSettingObject()->GetHashCalculator() + ); + { + TimeRecorder DiffTR(TEXT("Base Version And Diff Version total time")); + TMap BackupExternalFiles = Context.VersionDiff.PlatformExternDiffInfo; + Context.VersionDiff = UFlibHotPatcherCoreHelper::DiffPatchVersionWithPatchSetting(*Context.GetSettingObject(), Context.BaseVersion, DiffVersion); + Context.VersionDiff.PlatformExternDiffInfo = BackupExternalFiles; + } + } + return true; + } + // setup 4 + bool PatchRequireChekerWorker(FHotPatcherPatchContext& Context) + { + SCOPED_NAMED_EVENT_TEXT("PatchRequireChekerWorker",FColor::Red); + bool result = true; + TimeRecorder CheckRequireTR(TEXT("Check Patch Require")); + FString ReceiveMsg; + if (!Context.GetSettingObject()->IsCookPatchAssets() && + !UFlibHotPatcherCoreHelper::CheckPatchRequire( + Context.GetSettingObject()->GetStorageCookedDir(), + Context.VersionDiff,Context.GetSettingObject()->GetPakTargetPlatformNames(), ReceiveMsg)) + { + Context.OnShowMsg.Broadcast(ReceiveMsg); + result = false; + } + return result; + }; + + // setup 5 + bool SavePakVersionWorker(FHotPatcherPatchContext& Context) + { + SCOPED_NAMED_EVENT_TEXT("SavePakVersionWorker",FColor::Red); + // save pakversion.json + TimeRecorder SavePakVersionTR(TEXT("Save Pak Version")); + if(Context.GetSettingObject()->IsIncludePakVersion()) + { + FPakVersion CurrentPakVersion; + auto SavePatchVersionJson = [&Context](const FHotPatcherVersion& InSaveVersion, const FString& InSavePath, FPakVersion& OutPakVersion)->bool + { + bool bStatus = false; + OutPakVersion = FExportPatchSettings::GetPakVersion(InSaveVersion, FDateTime::UtcNow().ToString()); + { + if (Context.GetSettingObject()->IsIncludePakVersion()) + { + FString SavePakVersionFilePath = FExportPatchSettings::GetSavePakVersionPath(InSavePath, InSaveVersion); + + FString OutString; + if (THotPatcherTemplateHelper::TSerializeStructAsJsonString(OutPakVersion,OutString)) + { + bStatus = UFlibAssetManageHelper::SaveStringToFile(SavePakVersionFilePath, OutString); + } + } + } + return bStatus; + }; + + SavePatchVersionJson(Context.CurrentVersion, Context.GetSettingObject()->GetCurrentVersionSavePath(), CurrentPakVersion); + FPakCommand VersionCmd; + FString AbsPath = FExportPatchSettings::GetSavePakVersionPath(Context.GetSettingObject()->GetCurrentVersionSavePath(), Context.CurrentVersion); + FString MountPath = Context.GetSettingObject()->GetPakVersionFileMountPoint(); + VersionCmd.MountPath = MountPath; + VersionCmd.PakCommands = TArray{ + FString::Printf(TEXT("\"%s\" \"%s\""),*AbsPath,*MountPath) + }; + VersionCmd.AssetPackage = UFlibPatchParserHelper::MountPathToRelativePath(MountPath); + Context.AdditionalFileToPak.AddUnique(VersionCmd); + + UE_LOG(LogHotPatcher,Display,TEXT("Save current patch pakversion.json to %s ..."),*AbsPath); + } + return true; + }; + // setup 6 + bool ParserChunkWorker(FHotPatcherPatchContext& Context) + { + SCOPED_NAMED_EVENT_TEXT("ParserChunkWorker",FColor::Red); + // Check Chunk + if(Context.GetSettingObject()->IsEnableChunk()) + { + TimeRecorder AnalysisChunkTR(TEXT("Analysis Chunk Info(enable chunk)")); + FString TotalMsg; + FChunkInfo TotalChunk = UFlibPatchParserHelper::CombineChunkInfos(Context.GetSettingObject()->GetChunkInfos()); + + FChunkAssetDescribe ChunkDiffInfo = UFlibHotPatcherCoreHelper::DiffChunkWithPatchSetting( + *Context.GetSettingObject(), + Context.NewVersionChunk, + TotalChunk + ); + + if(ChunkDiffInfo.HasValidAssets()) + { + if(Context.GetSettingObject()->IsCreateDefaultChunk()) + { + + Context.PakChunks.Add(ChunkDiffInfo.AsChunkInfo(TEXT("Default"))); + } + else + { + TArray AllUnselectedAssets = ChunkDiffInfo.GetAssetsStrings(); + TArray AllUnselectedExFiles; + for(auto Platform:Context.GetSettingObject()->GetPakTargetPlatforms()) + { + AllUnselectedExFiles.Append(ChunkDiffInfo.GetExternalFileNames(Platform)); + } + + TArray UnSelectedInternalFiles = ChunkDiffInfo.GetInternalFileNames(); + + auto ChunkCheckerMsg = [&TotalMsg](const FString& Category,const TArray& InAssetList) + { + if (!!InAssetList.Num()) + { + TotalMsg.Append(FString::Printf(TEXT("\n%s:\n"),*Category)); + for (const auto& Asset : InAssetList) + { + TotalMsg.Append(FString::Printf(TEXT("%s\n"), *Asset.ToString())); + } + } + }; + ChunkCheckerMsg(TEXT("Unreal Asset"), AllUnselectedAssets); + ChunkCheckerMsg(TEXT("External Files"), AllUnselectedExFiles); + ChunkCheckerMsg(TEXT("Internal Files(Patch & Chunk setting not match)"), UnSelectedInternalFiles); + + if (!TotalMsg.IsEmpty()) + { + Context.OnShowMsg.Broadcast(FString::Printf(TEXT("Unselect in Chunk:\n%s"), *TotalMsg)); + return false; + } + else + { + Context.OnShowMsg.Broadcast(TEXT("")); + } + } + } + } + + bool bEnableChunk = Context.GetSettingObject()->IsEnableChunk(); + + // TArray PakChunks; + if (bEnableChunk) + { + Context.PakChunks.Append(Context.GetSettingObject()->GetChunkInfos()); + } + else + { + Context.PakChunks.Add(Context.NewVersionChunk); + } + return !!Context.PakChunks.Num(); + }; + + bool PreCookPatchAssets(FHotPatcherPatchContext& Context) + { + SCOPED_NAMED_EVENT_TEXT("PreCookPatchAssets",FColor::Red); + // wait global / compiling shader by load asset + UFlibShaderCodeLibraryHelper::WaitShaderCompilingComplete(); + + return true; + } + + bool PostCookPatchAssets(FHotPatcherPatchContext& Context) + { + SCOPED_NAMED_EVENT_TEXT("PostCookPatchAssets",FColor::Red); + if(Context.GetSettingObject()->GetCookShaderOptions().bSharedShaderLibrary) + { + for(const auto& PlatformName :Context.GetSettingObject()->GetPakTargetPlatformNames()) + { + for(auto& Chunk:Context.PakChunks) + { + FString ChunkSavedDir = Context.GetSettingObject()->GetChunkSavedDir(Context.CurrentVersion.VersionId,Context.CurrentVersion.BaseVersionId,Chunk.ChunkName,PlatformName); + FString SavePath = FPaths::Combine(ChunkSavedDir,TEXT("Metadatas"),PlatformName,TEXT("Metadata/ShaderLibrarySource")); + TArray FoundShaderLibs = UFlibShaderCodeLibraryHelper::FindCookedShaderLibByPlatform(PlatformName,SavePath); + + if(Context.PakChunks.Num()) + { + for(const auto& FilePath:FoundShaderLibs) + { + if(!Context.GetSettingObject()->GetCookShaderOptions().bNativeShaderToPak && + (FilePath.EndsWith(TEXT("metallib")) || FilePath.EndsWith(TEXT("metalmap")))) + { + // don't add metalib and metalmap to pak + continue; + } + FExternFileInfo AddShaderLib; + { + FString FileName = FPaths::GetBaseFilename(FilePath,true); + FString FileExtersion = FPaths::GetExtension(FilePath,false); + + AddShaderLib.Type = EPatchAssetType::NEW; + AddShaderLib.FilePath.FilePath = FPaths::ConvertRelativePathToFull(FilePath); + AddShaderLib.MountPath = FPaths::Combine( + UFlibPatchParserHelper::ParserMountPointRegular(Chunk.CookShaderOptions.GetShaderLibMountPointRegular()), + FString::Printf(TEXT("%s.%s"),*FileName,*FileExtersion) + ); + } + Context.AddExternalFile(PlatformName,Chunk.ChunkName,AddShaderLib); + } + } + } + } + } + return true; + } + + // setup 7 + bool CookPatchAssetsWorker(FHotPatcherPatchContext& Context) + { + SCOPED_NAMED_EVENT_TEXT("CookPatchAssetsWorker",FColor::Red); + TimeRecorder CookAssetsTotalTR(FString::Printf(TEXT("Cook All Assets in Patch Total time:"))); + + for(const auto& PlatformName :Context.GetSettingObject()->GetPakTargetPlatformNames()) + { + for(auto& Chunk:Context.PakChunks) + { + TimeRecorder CookPlatformChunkAssetsTotalTR(FString::Printf(TEXT("Cook Chunk %s as %s Total time:"),*Chunk.ChunkName,*PlatformName)); + if(Context.GetSettingObject()->IsCookPatchAssets() || Context.GetSettingObject()->GetIoStoreSettings().bIoStore) + { + TimeRecorder CookAssetsTR(FString::Printf(TEXT("Cook %s assets in the patch."),*PlatformName)); + + ETargetPlatform Platform; + THotPatcherTemplateHelper::GetEnumValueByName(PlatformName,Platform); + + FChunkAssetDescribe ChunkAssetsDescrible = UFlibPatchParserHelper::CollectFChunkAssetsDescribeByChunk( + Context.GetSettingObject(), + Context.VersionDiff, + Chunk, TArray{Platform} + ); + + const TArray& ChunkAssets = ChunkAssetsDescrible.Assets.GetAssetDetails(); + Context.PatchProxy->GetPatcherResult().PatcherAssetDetails.Append(ChunkAssets); + bool bSharedShaderLibrary = Context.GetSettingObject()->GetCookShaderOptions().bSharedShaderLibrary; + if(Context.GetSettingObject()->IsCookPatchAssets() || bSharedShaderLibrary) + { + TArray AllShaderAssets; + bool bOnlyCookShaders = !Context.GetSettingObject()->IsCookPatchAssets() && bSharedShaderLibrary; + if(bOnlyCookShaders) // only cook shaders + { + SCOPED_NAMED_EVENT_TEXT("ParserAllShaderAssets",FColor::Red); + TSet ShadersClasses = UFlibHotPatcherCoreHelper::GetAllMaterialClassesNames(); + for(const auto& Asset:ChunkAssets){ + if(ShadersClasses.Contains(Asset.AssetType)){ AllShaderAssets.Add(Asset);} + } + } + FTrackPackageAction TrackChunkPackageAction(Context,Chunk,TArray{Platform}); + FSingleCookerSettings EmptySetting; + EmptySetting.MissionID = 0; + EmptySetting.MissionName = FString::Printf(TEXT("%s_Cooker_%s"),FApp::GetProjectName(),*Chunk.ChunkName); + EmptySetting.ShaderLibName = Chunk.GetShaderLibraryName(); + EmptySetting.CookTargetPlatforms = TArray{Platform}; + EmptySetting.CookAssets = bOnlyCookShaders ? AllShaderAssets: ChunkAssets; + // EmptySetting.ForceSkipClasses = {}; + EmptySetting.bPackageTracker = Context.GetSettingObject()->IsPackageTracker(); + EmptySetting.ShaderOptions.bSharedShaderLibrary = Context.GetSettingObject()->GetCookShaderOptions().bSharedShaderLibrary; + EmptySetting.ShaderOptions.bNativeShader = Context.GetSettingObject()->GetCookShaderOptions().bNativeShader; + EmptySetting.ShaderOptions.bMergeShaderLibrary = false; + EmptySetting.IoStoreSettings = Context.GetSettingObject()->GetIoStoreSettings(); + EmptySetting.IoStoreSettings.bStorageBulkDataInfo = false;// dont save platform context data to disk + EmptySetting.bSerializeAssetRegistry = Context.GetSettingObject()->GetSerializeAssetRegistryOptions().bSerializeAssetRegistry; + EmptySetting.bPreGeneratePlatformData = Context.GetSettingObject()->IsCookParallelSerialize(); + EmptySetting.bWaitEachAssetCompleted = Context.GetSettingObject()->IsCookParallelSerialize(); + EmptySetting.bConcurrentSave = Context.GetSettingObject()->IsCookParallelSerialize(); + // for current impl arch + EmptySetting.bForceCookInOneFrame = true; + EmptySetting.NumberOfAssetsPerFrame = Context.GetSettingObject()->CookAdvancedOptions.NumberOfAssetsPerFrame; + EmptySetting.OverrideNumberOfAssetsPerFrame = Context.GetSettingObject()->CookAdvancedOptions.GetOverrideNumberOfAssetsPerFrame(); + EmptySetting.bDisplayConfig = false; + EmptySetting.StorageCookedDir = Context.GetSettingObject()->GetStorageCookedDir();//FPaths::Combine(FPaths::ConvertRelativePathToFull(FPaths::ProjectSavedDir()),TEXT("Cooked")); + + FString ChunkSavedDir = Context.GetSettingObject()->GetChunkSavedDir(Context.CurrentVersion.VersionId,Context.CurrentVersion.BaseVersionId,Chunk.ChunkName,PlatformName); + EmptySetting.StorageMetadataDir = FPaths::Combine(ChunkSavedDir,TEXT("Metadatas")); +#if WITH_PACKAGE_CONTEXT + EmptySetting.bOverrideSavePackageContext = true; + EmptySetting.PlatformSavePackageContexts = Context.PatchProxy->GetPlatformSavePackageContexts(); +#endif + USingleCookerProxy* SingleCookerProxy = NewObject(); + SingleCookerProxy->AddToRoot(); + SingleCookerProxy->Init(&EmptySetting); + bool bExportStatus = SingleCookerProxy->DoExport(); + const FCookCluster& AdditionalCluster = SingleCookerProxy->GetPackageTrackerAsCluster(); + for(const auto& AssetDetail:AdditionalCluster.AssetDetails) + { + FSoftObjectPath ObjectPath{AssetDetail.PackagePath}; + bool bContainInBaseVersion = FTrackPackageAction::IsAssetContainIn(Context.BaseVersion.AssetInfo,AssetDetail); + + FString ReceiveReason; + if(!Context.GetSettingObject()->GetAssetScanConfig().IsMatchForceSkip(ObjectPath,ReceiveReason) && !bContainInBaseVersion) + { + Context.PatchProxy->GetPatcherResult().PatcherAssetDetails.Add(AssetDetail); + Context.VersionDiff.AssetDiffInfo.AddAssetDependInfo.AddAssetsDetail(AssetDetail); + } + else + { + UE_LOG(LogHotPatcher,Display,TEXT("[PackageTracker] %s Match ForceSkipRule,Reason %s"),*ObjectPath.GetLongPackageName(),*ReceiveReason); + } + } + + SingleCookerProxy->Shutdown(); + SingleCookerProxy->RemoveFromRoot(); + } + } + } + } + + return true; + }; + + bool PatchAssetRegistryWorker(FHotPatcherPatchContext& Context) + { + SCOPED_NAMED_EVENT_TEXT("PatchAssetRegistryWorker",FColor::Red); + if(Context.GetSettingObject()->GetSerializeAssetRegistryOptions().bSerializeAssetRegistry) + { + auto SerializeAssetRegistry = [](FHotPatcherPatchContext& Context,const FChunkInfo& Chunk,const FString& PlatformName) + { + ETargetPlatform Platform; + THotPatcherTemplateHelper::GetEnumValueByName(PlatformName,Platform); + FChunkAssetDescribe ChunkAssetsDescrible = UFlibPatchParserHelper::CollectFChunkAssetsDescribeByChunk( + Context.GetSettingObject(), + Context.VersionDiff, + Chunk, TArray{Platform} + ); + const TArray& ChunkAssets = ChunkAssetsDescrible.Assets.GetAssetDetails(); + + if(Context.GetSettingObject()->GetSerializeAssetRegistryOptions().bSerializeAssetRegistry) + { + FString ChunkAssetRegistryName = Context.GetSettingObject()->GetSerializeAssetRegistryOptions().GetAssetRegistryNameRegular(Chunk.ChunkName); + // // Save the generated registry + FString AssetRegistryPath = FPaths::Combine( + Context.GetSettingObject()->GetChunkSavedDir(Context.CurrentVersion.VersionId,Context.BaseVersion.VersionId,Context.NewVersionChunk.ChunkName,PlatformName), + ChunkAssetRegistryName); + if(UFlibHotPatcherCoreHelper::SerializeAssetRegistryByDetails(Context.PatchProxy->GetAssetRegistry(),PlatformName,ChunkAssets, AssetRegistryPath)) + { + FExternFileInfo AssetRegistryFileInfo; + AssetRegistryFileInfo.Type = EPatchAssetType::NEW; + AssetRegistryFileInfo.FilePath.FilePath = AssetRegistryPath; + AssetRegistryFileInfo.MountPath = FPaths::Combine( + UFlibPatchParserHelper::ParserMountPointRegular(Context.GetSettingObject()->GetSerializeAssetRegistryOptions().GetAssetRegistryMountPointRegular()) + ,ChunkAssetRegistryName + ); + Context.GetPatcherDiffInfoByName(PlatformName)->AddExternalFiles.Add(AssetRegistryFileInfo); + Context.GetPatcherChunkInfoByName(PlatformName,Chunk.ChunkName)->AddExternFileToPak.Add(AssetRegistryFileInfo); + } + } + }; + + switch(Context.GetSettingObject()->GetSerializeAssetRegistryOptions().AssetRegistryRule) + { + case EAssetRegistryRule::PATCH: + { + for(const auto& PlatformName:Context.GetSettingObject()->GetPakTargetPlatformNames()) + { + SerializeAssetRegistry(Context,Context.NewVersionChunk,PlatformName); + } + break; + } + case EAssetRegistryRule::PER_CHUNK: + { + for(const auto& PlatformName:Context.GetSettingObject()->GetPakTargetPlatformNames()) + { + for(const auto& Chunk:Context.PakChunks) + { + SerializeAssetRegistry(Context,Chunk,PlatformName); + } + } + break; + } + default:{} + }; + } + return true; + }; + bool GenerateGlobalAssetRegistryManifest(FHotPatcherPatchContext& Context) + { + SCOPED_NAMED_EVENT_TEXT("GenerateGlobalAssetRegistryManifest",FColor::Red); + if(Context.GetSettingObject()->GetSerializeAssetRegistryOptions().bSerializeAssetRegistryManifest) + { + FAssetDependenciesInfo TotalDiffAssets = UFlibAssetManageHelper::CombineAssetDependencies(Context.VersionDiff.AssetDiffInfo.AddAssetDependInfo,Context.VersionDiff.AssetDiffInfo.ModifyAssetDependInfo); + const TArray& AllAssets = TotalDiffAssets.GetAssetDetails(); + + TSet PackageAssetsSet; + for(const auto& Asset:AllAssets) + { + FString LongPackageName = UFlibAssetManageHelper::PackagePathToLongPackageName(Asset.PackagePath.ToString()); + PackageAssetsSet.Add(*LongPackageName); + } + for(const auto& PlatformName:Context.GetSettingObject()->GetPakTargetPlatformNames()) + { + ITargetPlatform* PlatformIns = UFlibHotPatcherCoreHelper::GetPlatformByName(PlatformName); + + UFlibHotPatcherCoreHelper::SerializeChunksManifests(PlatformIns,PackageAssetsSet,TSet{},true); + } + } + + return true; + } + + void GenerateBinariesPatch(FHotPatcherPatchContext& Context,FChunkInfo& Chunk,ETargetPlatform Platform,TArray& PakCommands) + { + SCOPED_NAMED_EVENT_TEXT("GenerateBinariesPatch",FColor::Red); + TArray ModularFeatures = IModularFeatures::Get().GetModularFeatureImplementations(BINARIES_DIFF_PATCH_FEATURE_NAME); + if(!ModularFeatures.Num()) + return; + IBinariesDiffPatchFeature* UseFeature = ModularFeatures[0]; + for(const auto& Feature: ModularFeatures) + { + if(!Context.GetSettingObject()->GetBinariesPatchConfig().GetBinariesPatchFeatureName().IsEmpty() && + Feature->GetFeatureName().Equals(Context.GetSettingObject()->GetBinariesPatchConfig().GetBinariesPatchFeatureName(),ESearchCase::IgnoreCase)) + { + UseFeature = Feature; + break; + } + } + UE_LOG(LogHotPatcher,Log,TEXT("Use BinariesPatchFeature %s"),*UseFeature->GetFeatureName()); + FString PlatformName = THotPatcherTemplateHelper::GetEnumNameByValue(Platform); + UHotPatcherSettings* Settings = GetMutableDefault(); + FString TempSavedPath = Settings->GetTempSavedDir(); + TimeRecorder BinariesPatchToralTR(FString::Printf(TEXT("Generate Binaries Patch of %s all chunks Total Time:"),*PlatformName)); + FString OldCookedDir = Context.GetSettingObject()->GetBinariesPatchConfig().GetOldCookedDir(); + if(!FPaths::FileExists(OldCookedDir)) + { + OldCookedDir = FPaths::ConvertRelativePathToFull(FPaths::Combine(TempSavedPath,Context.BaseVersion.VersionId)); + } + + FString ExtractCryptoFile = Context.GetSettingObject()->GetBinariesPatchConfig().GetBasePakExtractCryptoJson(); + FString ExtractCryptoCmd = FPaths::FileExists(ExtractCryptoFile) ? FString::Printf(TEXT("-cryptokeys=\"%s\""),*ExtractCryptoFile) : TEXT(""); + FString ExtractDir = FPaths::ConvertRelativePathToFull(FPaths::Combine(TempSavedPath,Context.BaseVersion.VersionId,PlatformName)); + // Extract Asset by Base Version Paks + auto ExtractByPak = [&Context,Platform,ExtractDir,ExtractCryptoCmd,ExtractCryptoFile](const FPakCommand& PakCommand)->bool + { + FString PakCommandMountOnlyPath; + { + FString postfix; + PakCommand.MountPath.Split(TEXT("."),&PakCommandMountOnlyPath,&postfix,ESearchCase::IgnoreCase,ESearchDir::FromEnd); + } + + TArray CurrentPlatformPaks = Context.GetSettingObject()->GetBinariesPatchConfig().GetBaseVersionPakByPlatform(Platform); + bool bExtraced = false; + for(const auto& Pak:CurrentPlatformPaks) + { + if(!FPaths::FileExists(Pak)) + continue; + FString AESKeyString = UFlibPatchParserHelper::LoadAESKeyStringFromCryptoFile(ExtractCryptoFile); + FString PakMountPoint = UFlibPakHelper::GetPakFileMountPoint(Pak,AESKeyString); + if(!(!PakMountPoint.IsEmpty() && PakCommand.MountPath.StartsWith(PakMountPoint))) + { + // file not in the pak + continue; + } + + FString ExtractFilter = FString::Printf( + TEXT("-Filter=\"%s.*\""), + *UKismetStringLibrary::GetSubstring(PakCommandMountOnlyPath,PakMountPoint.Len(),PakCommandMountOnlyPath.Len() - PakMountPoint.Len()) + ); + FString RelativeMountPoint = PakMountPoint; + while(RelativeMountPoint.RemoveFromStart(TEXT("../"))){} + FString FinalExtractDir = FPaths::Combine(ExtractDir,RelativeMountPoint); + FPaths::NormalizeFilename(FinalExtractDir); + FString FinalCommand = FString::Printf(TEXT("-Extract \"%s\" \"%s\" %s %s"),*Pak,*FinalExtractDir,*ExtractCryptoCmd,*ExtractFilter); + UE_LOG(LogHotPatcher,Log,TEXT("Extract Base Version Pak Command: %s"),*FinalCommand); + if(ExecuteUnrealPak(*FinalCommand)) + { + bExtraced = true; + } + } + return bExtraced; + }; + + for(auto& PakCommand:PakCommands) + { + if(PakCommand.Type == EPatchAssetType::NEW || PakCommand.Type == EPatchAssetType::None) + continue; + if(!ExtractByPak(PakCommand)) + continue; + TArray PatchedPakCommand; + for(const auto& PakAssetPath: PakCommand.PakCommands) + { + auto RemoveDoubleQuoteLambda = [](const FString& InStr)->FString + { + FString resultStr = InStr; + if(resultStr.StartsWith(TEXT("\""))) + { + resultStr.RemoveAt(0); + } + if(resultStr.EndsWith(TEXT("\""))) + { + resultStr.RemoveAt(resultStr.Len() - 1); + } + return resultStr; + }; + + auto ParseUassetLambda = [&RemoveDoubleQuoteLambda](const FString& InAsset)->FPakCommandItem + { + FPakCommandItem result; + TArray AssetPakCmd = UKismetStringLibrary::ParseIntoArray(InAsset,TEXT("\" ")); + + FString AssetAbsPath = AssetPakCmd[0]; + FString AssetMountPath = AssetPakCmd[1]; + result.AssetAbsPath = RemoveDoubleQuoteLambda(AssetAbsPath); + result.AssetMountPath = RemoveDoubleQuoteLambda(AssetMountPath); + return result; + }; + + FPakCommandItem PakAssetInfo = ParseUassetLambda(PakAssetPath); + if(Context.GetSettingObject()->GetBinariesPatchConfig().IsMatchIgnoreRules(PakAssetInfo)) + { + PatchedPakCommand.AddUnique(PakAssetPath); + continue; + } + FString ProjectCookedDir = Context.GetSettingObject()->GetStorageCookedDir();// FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectSavedDir(),TEXT("Cooked"))); + FString OldAsset = PakAssetInfo.AssetAbsPath.Replace( + *ProjectCookedDir, + *OldCookedDir,ESearchCase::CaseSensitive); + FString PatchSaveToPath = PakAssetInfo.AssetAbsPath.Replace( + *ProjectCookedDir, + *FPaths::ConvertRelativePathToFull(FPaths::Combine(Settings->GetTempSavedDir(),TEXT("BinariesPatch"))) + ) + TEXT(".patch"); + FString PatchSaveToMountPath = PakAssetInfo.AssetMountPath + TEXT(".patch"); + bool bPatch = false; + if(FPaths::FileExists(PakAssetInfo.AssetAbsPath) && FPaths::FileExists(OldAsset)) + { + TArray OldAssetData; + TArray NewAssetData; + TArray PatchData; + bool bLoadOld = FFileHelper::LoadFileToArray(OldAssetData,*OldAsset); + bool bLoadNew = FFileHelper::LoadFileToArray(NewAssetData,*PakAssetInfo.AssetAbsPath); + + if(UseFeature->CreateDiff(NewAssetData,OldAssetData,PatchData)) + { + if(FFileHelper::SaveArrayToFile(PatchData,*PatchSaveToPath)) + { + PatchedPakCommand.AddUnique(FString::Printf(TEXT("\"%s\" \"%s\""),*PatchSaveToPath,*PatchSaveToMountPath)); + bPatch = true; + } + } + } + if(!bPatch) + { + PatchedPakCommand.AddUnique(PakAssetPath); + } + } + if(!!PatchedPakCommand.Num()) + { + PakCommand.PakCommands = PatchedPakCommand; + } + } + } + + // setup 8 + bool GeneratePakProxysWorker(FHotPatcherPatchContext& Context) + { + SCOPED_NAMED_EVENT_TEXT("GeneratePakProxysWorker",FColor::Red); + TimeRecorder PakChunkToralTR(FString::Printf(TEXT("Generate all platform pakproxys of all chunks Total Time:"))); + TArray PakPlatforms = Context.GetSettingObject()->GetPakTargetPlatforms(); + for(const auto& Platform :PakPlatforms) + { + FString PlatformName = THotPatcherTemplateHelper::GetEnumNameByValue(Platform); + // PakModeSingleLambda(PlatformName, CurrentVersionSavePath); + for (auto& Chunk : Context.PakChunks) + { + TimeRecorder PakChunkTR(FString::Printf(TEXT("Generate Chunk Platform:%s ChunkName:%s PakProxy Time:"),*PlatformName,*Chunk.ChunkName)); + // Update Progress Dialog + { + FText Dialog = FText::Format(NSLOCTEXT("ExportPatch", "GeneratedPakCommands", "Generating UnrealPak Commands of {0} Platform Chunk {1}."), FText::FromString(PlatformName),FText::FromString(Chunk.ChunkName)); + Context.OnPaking.Broadcast(TEXT("ExportPatch"),*Dialog.ToString()); + Context.UnrealPakSlowTask->EnterProgressFrame(1.0, Dialog); + } + + FString ChunkSaveBasePath = Context.GetSettingObject()->GetChunkSavedDir(Context.CurrentVersion.VersionId,Context.CurrentVersion.BaseVersionId,Chunk.ChunkName,PlatformName); + + TArray ChunkPakListCommands; + { + TimeRecorder CookAssetsTR(FString::Printf(TEXT("CollectPakCommandByChunk Platform:%s ChunkName:%s."),*PlatformName,*Chunk.ChunkName)); + ChunkPakListCommands= UFlibPatchParserHelper::CollectPakCommandByChunk( + Context.VersionDiff, + Chunk, + PlatformName, + Context.GetSettingObject() + ); + } + + if(Context.PatchProxy) + Context.PatchProxy->OnPakListGenerated.Broadcast(Context,Chunk,Platform,ChunkPakListCommands); + + TArray IgnoreCompressFormats = UFlibHotPatcherCoreHelper::GetExtensionsToNotUsePluginCompressionByGConfig(); + for(auto& PakCommand:ChunkPakListCommands) + { + TArray EmptyArray; + TArray CompressOption{TEXT("-compress")}; + + auto AppendPakCommandOptions = [&Context](FPakCommand& PakCommand, + const TArray& Options, + bool bAppendAllMatch, + const TArray& AppendFileExtersions, + const TArray& IgnoreFormats, // 忽略追加Option的文件格式 + const TArray& InIgnoreFormatsOptions // 给忽略的格式忽略追加哪些option,如-compress不在ini中添加 + ) + { + UFlibHotPatcherCoreHelper::AppendPakCommandOptions( + PakCommand.PakCommands, + Options, + bAppendAllMatch, + AppendFileExtersions, + IgnoreFormats, + InIgnoreFormatsOptions + ); + UFlibHotPatcherCoreHelper::AppendPakCommandOptions( + PakCommand.IoStoreCommands, + Options, + bAppendAllMatch, + AppendFileExtersions, + IgnoreFormats, + InIgnoreFormatsOptions + ); + }; + // 给所有的文件添加PakCommand的Option参数,默认只包含-compress,除了ExtensionsToNotUsePluginCompression中配置的文件都会添加 + UFlibHotPatcherCoreHelper::AppendPakCommandOptions(PakCommand.PakCommands,Context.GetSettingObject()->GetUnrealPakSettings().UnrealPakListOptions,true,EmptyArray,IgnoreCompressFormats,CompressOption); + UFlibHotPatcherCoreHelper::AppendPakCommandOptions(PakCommand.PakCommands,Context.GetSettingObject()->GetDefaultPakListOptions(),true,EmptyArray,IgnoreCompressFormats,CompressOption); + UFlibHotPatcherCoreHelper::AppendPakCommandOptions(PakCommand.IoStoreCommands,Context.GetSettingObject()->GetIoStoreSettings().IoStorePakListOptions,true,EmptyArray,IgnoreCompressFormats,CompressOption); + UFlibHotPatcherCoreHelper::AppendPakCommandOptions(PakCommand.IoStoreCommands,Context.GetSettingObject()->GetDefaultPakListOptions(),true,EmptyArray,IgnoreCompressFormats,CompressOption); + + FEncryptSetting EncryptSettings = UFlibPatchParserHelper::GetCryptoSettingByPakEncryptSettings(Context.GetSettingObject()->GetEncryptSettings()); + + // 加密所有文件 + if(EncryptSettings.bEncryptAllAssetFiles) + { + TArray EncryptOption{TEXT("-encrypt")}; + AppendPakCommandOptions(PakCommand,EncryptOption,true,EmptyArray,EmptyArray,EmptyArray); + } + else + { + // 加密 uasset + if(EncryptSettings.bEncryptUAssetFiles) + { + TArray EncryptOption{TEXT("-encrypt")}; + TArray EncryptFileExtersion{TEXT("uasset")}; + + AppendPakCommandOptions(PakCommand,EncryptOption,false,EncryptFileExtersion,EmptyArray,EmptyArray); + } + // 加密 ini + if(EncryptSettings.bEncryptIniFiles) + { + TArray EncryptOption{TEXT("-encrypt")}; + TArray EncryptFileExtersion{TEXT("ini")}; + AppendPakCommandOptions(PakCommand,EncryptOption,false,EncryptFileExtersion,EmptyArray,EmptyArray); + } + } + } + + if (!ChunkPakListCommands.Num()) + { + FString Msg = FString::Printf(TEXT("Chunk:%s not contain any file!!!"), *Chunk.ChunkName); + UE_LOG(LogHotPatcher, Warning, TEXT("%s"),*Msg); + Context.OnShowMsg.Broadcast(Msg); + continue; + } + + if(!Chunk.bMonolithic) + { + FPakFileProxy SinglePakForChunk; + SinglePakForChunk.Platform = Platform; + SinglePakForChunk.PakCommands = ChunkPakListCommands; + // add extern file to pak(version file) + SinglePakForChunk.PakCommands.Append(Context.AdditionalFileToPak); + + FReplacePakRegular PakPathRegular{ + Context.CurrentVersion.VersionId, + Context.CurrentVersion.BaseVersionId, + Chunk.ChunkName, + PlatformName + }; + + const FString ChunkPakName = UFlibHotPatcherCoreHelper::ReplacePakRegular(PakPathRegular,Context.GetSettingObject()->GetPakNameRegular()); + SinglePakForChunk.ChunkStoreName = ChunkPakName; + SinglePakForChunk.StorageDirectory = ChunkSaveBasePath; + Chunk.GetPakFileProxys().Add(SinglePakForChunk); + } + else + { + for (const auto& PakCommand : ChunkPakListCommands) + { + FPakFileProxy CurrentPak; + CurrentPak.Platform = Platform; + CurrentPak.PakCommands.Add(PakCommand); + // add extern file to pak(version file) + CurrentPak.PakCommands.Append(Context.AdditionalFileToPak); + FString Path; + switch (Chunk.MonolithicPathMode) + { + case EMonolithicPathMode::MountPath: + { + Path = UFlibPatchParserHelper::MountPathToRelativePath(PakCommand.GetMountPath()); + break; + + }; + case EMonolithicPathMode::PackagePath: + { + Path = PakCommand.AssetPackage; + break; + } + } + CurrentPak.ChunkStoreName = Path; + CurrentPak.StorageDirectory = FPaths::Combine(ChunkSaveBasePath, Chunk.ChunkName); + Chunk.GetPakFileProxys().Add(CurrentPak); + } + } + } + } + return true; + }; + + // setup 9 + bool CreatePakWorker(FHotPatcherPatchContext& Context) + { + SCOPED_NAMED_EVENT_TEXT("CreatePakWorker",FColor::Red); + TimeRecorder CreateAllPakToralTR(FString::Printf(TEXT("Generate all platform pak of all chunks Total Time:"))); + // PakModeSingleLambda(PlatformName, CurrentVersionSavePath); + for (const auto& Chunk : Context.PakChunks) + { + TimeRecorder PakChunkToralTR(FString::Printf(TEXT("Package ChunkName:%s Total Time:"),*Chunk.ChunkName)); + + // // Update SlowTask Progress + { + FText Dialog = FText::Format(NSLOCTEXT("ExportPatch_GeneratedPakList", "GeneratedPak", "Generating Pak list of Chunk {0}."), FText::FromString(Chunk.ChunkName)); + Context.OnPaking.Broadcast(TEXT("GeneratedPak"),*Dialog.ToString()); + Context.UnrealPakSlowTask->EnterProgressFrame(1.0, Dialog); + } + + TArray UnrealPakCommandletGeneralOptions = Context.GetSettingObject()->GetUnrealPakSettings().UnrealCommandletOptions; + UnrealPakCommandletGeneralOptions.Append(Context.GetSettingObject()->GetDefaultCommandletOptions()); + + TArray ReplacePakListTexts = Context.GetSettingObject()->GetReplacePakListTexts(); + TArray PakWorker; + + // TMap>& PakFilesInfoMap = Context.PakFilesInfoMap; + + // 创建chunk的pak文件 + for (const auto& PakFileProxy : Chunk.GetPakFileProxys()) + { + FString PlatformName = THotPatcherTemplateHelper::GetEnumNameByValue(PakFileProxy.Platform); + TArray UnrealPakCommandletOptions; + UnrealPakCommandletOptions.Add(FString::Printf(TEXT("-AlignForMemoryMapping=%d"),UFlibHotPatcherCoreHelper::GetMemoryMappingAlignment(PlatformName))); + // can override + UnrealPakCommandletOptions.Append(UnrealPakCommandletGeneralOptions); + UnrealPakCommandletOptions.Add(UFlibHotPatcherCoreHelper::GetEncryptSettingsCommandlineOptions(Context.GetSettingObject()->GetEncryptSettings(),UFlibHotPatcherCoreHelper::Conv2IniPlatform(PlatformName))); + TimeRecorder CookAssetsTR(FString::Printf(TEXT("Create Pak Platform:%s ChunkName:%s."),*PlatformName,*Chunk.ChunkName)); + // ++PakCounter; + uint32 index = PakWorker.Emplace(*PakFileProxy.ChunkStoreName, [/*CurrentPakVersion, */PlatformName, UnrealPakCommandletOptions, ReplacePakListTexts, PakFileProxy, &Chunk,&Context]() + { + FString PakListFile = FPaths::Combine(PakFileProxy.StorageDirectory, FString::Printf(TEXT("%s_PakCommands.txt"), *PakFileProxy.ChunkStoreName)); + FString PakSavePath = FPaths::Combine(PakFileProxy.StorageDirectory, FString::Printf(TEXT("%s.pak"), *PakFileProxy.ChunkStoreName)); + bool PakCommandSaveStatus = FFileHelper::SaveStringArrayToFile( + UFlibPatchParserHelper::GetPakCommandStrByCommands(PakFileProxy.PakCommands, ReplacePakListTexts,false), + *PakListFile, + FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM); + if (PakCommandSaveStatus) + { + TArray UnrealPakCommandletOptionsSinglePak; + UnrealPakCommandletOptionsSinglePak.Add( + FString::Printf( + TEXT("%s -create=%s"), + *(TEXT("\"") + PakSavePath + TEXT("\"")), + *(TEXT("\"") + PakListFile + TEXT("\"")) + ) + ); + UnrealPakCommandletOptionsSinglePak.Append(UnrealPakCommandletOptions); + + FString CommandLine; + for (const auto& Option : UnrealPakCommandletOptionsSinglePak) + { + CommandLine.Append(FString::Printf(TEXT(" %s"), *Option)); + } + Context.OnPaking.Broadcast(TEXT("Create Pak Commandline: %s"),CommandLine); + UE_LOG(LogHotPatcher,Log,TEXT("Create Pak Commandline: %s"),*CommandLine); + ExecuteUnrealPak(*CommandLine); + // FProcHandle ProcessHandle = UFlibPatchParserHelper::DoUnrealPak(UnrealPakCommandletOptionsSinglePak, true); + + // AsyncTask(ENamedThreads::GameThread, [this,PakFileProxy,&PakFilesInfoMap,PlatformName]() + { + if (FPaths::FileExists(PakSavePath)) + { + if(::IsRunningCommandlet()) + { + FString Msg = FString::Printf(TEXT("Package the Patch as %s."),*PakSavePath); + Context.OnPaking.Broadcast(TEXT("SavedPakFile"),Msg); + }else + { + FString MsgStr = FString::Printf(TEXT("Package %s for %s Successfuly."),*Chunk.ChunkName,*PlatformName); + FText Msg = UKismetTextLibrary::Conv_StringToText(MsgStr); + FHotPatcherDelegates::Get().GetNotifyFileGenerated().Broadcast(Msg,PakSavePath); + } + FPakFileInfo CurrentPakInfo; + if (UFlibPatchParserHelper::GetPakFileInfo(PakSavePath, CurrentPakInfo)) + { + // CurrentPakInfo.PakVersion = CurrentPakVersion; + if (!Context.PakFilesInfoMap.PakFilesMap.Contains(PlatformName)) + { + FPakFileArray PakFileArray; + PakFileArray.PakFileInfos = TArray{CurrentPakInfo}; + Context.PakFilesInfoMap.PakFilesMap.Add(PlatformName, PakFileArray); + } + else + { + Context.PakFilesInfoMap.PakFilesMap.Find(PlatformName)->PakFileInfos.Add(CurrentPakInfo); + } + } + } + }//); + + if (!(Chunk.bStorageUnrealPakList && Chunk.bOutputDebugInfo)) + { + IFileManager::Get().Delete(*PakListFile); + } + } + }); + PakWorker[index].Run(); + } + } + return true; + }; + + // setup 9 + bool CreateIoStoreWorker(FHotPatcherPatchContext& Context) + { + SCOPED_NAMED_EVENT_TEXT("CreateIoStoreWorker",FColor::Red); +#if WITH_IO_STORE_SUPPORT + if(!Context.GetSettingObject()->GetIoStoreSettings().bIoStore) + return true; + TimeRecorder CreateAllIoStoreToralTR(FString::Printf(TEXT("Generate all platform Io Store of all chunks Total Time:"))); + + TArray AdditionalIoStoreCommandletOptions; + AdditionalIoStoreCommandletOptions.Append(Context.GetSettingObject()->GetDefaultCommandletOptions()); + AdditionalIoStoreCommandletOptions.Append(Context.GetSettingObject()->GetIoStoreSettings().IoStoreCommandletOptions); + + FIoStoreSettings IoStoreSettings = Context.GetSettingObject()->GetIoStoreSettings(); + // PakModeSingleLambda(PlatformName, CurrentVersionSavePath); + for (const auto& Chunk : Context.PakChunks) + { + TimeRecorder PakChunkAllIoStoreToralTR(FString::Printf(TEXT("Package ChunkName:%s Io Store Total Time:"),*Chunk.ChunkName)); + + // // Update SlowTask Progress + { + FText Dialog = FText::Format(NSLOCTEXT("ExportPatch_GeneratedPak", "GeneratedPak", "Generating Io Store list of Chunk {0}."), FText::FromString(Chunk.ChunkName)); + Context.OnPaking.Broadcast(TEXT("GeneratedIoStore"),*Dialog.ToString()); + Context.UnrealPakSlowTask->EnterProgressFrame(1.0, Dialog); + } + + TArray ReplacePakListTexts = Context.GetSettingObject()->GetReplacePakListTexts(); + if(!Chunk.GetPakFileProxys().Num()) + { + UE_LOG(LogHotPatcher,Error,TEXT("Chunk %s Not Contain Any valid PakFileProxy!!!"),*Chunk.ChunkName); + continue; + } + // 创建chunk的Io Store文件 + TArray IoStoreCommands; + for (const auto& PakFileProxy : Chunk.GetPakFileProxys()) + { + if(!IoStoreSettings.PlatformContainers.Contains(PakFileProxy.Platform)) + return true; + TArray FinalIoStoreCommandletOptions; + FString PlatformName = THotPatcherTemplateHelper::GetEnumNameByValue(PakFileProxy.Platform); + FinalIoStoreCommandletOptions.Add(FString::Printf(TEXT("-AlignForMemoryMapping=%d"),UFlibHotPatcherCoreHelper::GetPlatformByName(PlatformName)->GetMemoryMappingAlignment())); + FinalIoStoreCommandletOptions.Append(AdditionalIoStoreCommandletOptions); + FString IoStoreCommandletOptions; + for(const auto& Option:FinalIoStoreCommandletOptions) { IoStoreCommandletOptions+=FString::Printf(TEXT("%s "),*Option); } + IoStoreCommandletOptions += UFlibHotPatcherCoreHelper::GetEncryptSettingsCommandlineOptions(Context.GetSettingObject()->GetEncryptSettings(),UFlibHotPatcherCoreHelper::Conv2IniPlatform(PlatformName)); + + TimeRecorder CookAssetsTR(FString::Printf(TEXT("Create Pak Platform:%s ChunkName:%s."),*PlatformName,*Chunk.ChunkName)); + FString PakListFile = FPaths::Combine(PakFileProxy.StorageDirectory, FString::Printf(TEXT("%s_IoStorePakList.txt"), *PakFileProxy.ChunkStoreName)); + FString PakSavePath = FPaths::Combine(PakFileProxy.StorageDirectory, FString::Printf(TEXT("%s.utoc"), *PakFileProxy.ChunkStoreName)); + + FString PatchSource; + + FString BasePacakgeRootDir = FPaths::ConvertRelativePathToFull(UFlibPatchParserHelper::ReplaceMarkPath(Context.GetSettingObject()->GetIoStoreSettings().PlatformContainers.Find(PakFileProxy.Platform)->BasePackageStagedRootDir.Path)); + if(FPaths::DirectoryExists(BasePacakgeRootDir)) + { + PatchSource = FPaths::Combine(BasePacakgeRootDir,FApp::GetProjectName(),FString::Printf(TEXT("Content/Paks/%s*.utoc"),FApp::GetProjectName())); + } + + FString PatchSoruceSetting = UFlibPatchParserHelper::ReplaceMarkPath(IoStoreSettings.PlatformContainers.Find(PakFileProxy.Platform)->PatchSourceOverride.FilePath); + if(FPaths::FileExists(PatchSoruceSetting)) + { + PatchSource = PatchSoruceSetting; + } + + bool PakCommandSaveStatus = FFileHelper::SaveStringArrayToFile( + UFlibPatchParserHelper::GetPakCommandStrByCommands(PakFileProxy.PakCommands, ReplacePakListTexts,true), + *PakListFile, + FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM); + FString PatchDiffCommand = IoStoreSettings.PlatformContainers.Find(PakFileProxy.Platform)->bGenerateDiffPatch ? + FString::Printf(TEXT("-PatchSource=\"%s\" -GenerateDiffPatch"),*PatchSource):TEXT(""); + + IoStoreCommands.Emplace( + FString::Printf(TEXT("-Output=\"%s\" -ContainerName=\"%s\" -ResponseFile=\"%s\" %s"), + *PakSavePath, + *PakFileProxy.ChunkStoreName, + *PakListFile, + *PatchDiffCommand + ) + ); +#if ENGINE_MAJOR_VERSION < 5 && ENGINE_MINOR_VERSION < 26 + FString OutputDirectoryCmd = FString::Printf(TEXT("-OutputDirectory=%s"),*FPaths::Combine(Context.GetSettingObject()->GetSaveAbsPath())); +#else + FString OutputDirectoryCmd = TEXT(""); +#endif + FString IoStoreCommandsFile = FPaths::Combine(Chunk.GetPakFileProxys()[0].StorageDirectory,FString::Printf(TEXT("%s_IoStoreCommands.txt"),*Chunk.ChunkName)); + + FString PlatformGlocalContainers; + // FString BasePacakgeRootDir = FPaths::ConvertRelativePathToFull(UFlibPatchParserHelper::ReplaceMarkPath(Context.GetSettingObject()->GetIoStoreSettings().PlatformContainers.Find(PakFileProxy.Platform)->BasePackageStagedRootDir.Path)); + if(FPaths::DirectoryExists(BasePacakgeRootDir)) + { + PlatformGlocalContainers = FPaths::Combine(BasePacakgeRootDir,FApp::GetProjectName(),TEXT("Content/Paks/global.utoc")); + } + FString GlobalUtocFile = UFlibPatchParserHelper::ReplaceMarkPath(IoStoreSettings.PlatformContainers.Find(PakFileProxy.Platform)->GlobalContainersOverride.FilePath); + if(FPaths::FileExists(GlobalUtocFile)) + { + PlatformGlocalContainers = GlobalUtocFile; + } + + FString PlatformCookDir = FPaths::ConvertRelativePathToFull(FPaths::Combine(Context.GetSettingObject()->GetStorageCookedDir(),PlatformName)); + + if(FPaths::FileExists(PlatformGlocalContainers)) + { + FString CookOrderFile = FPaths::Combine(Context.GetSettingObject()->GetSaveAbsPath(),Context.NewVersionChunk.ChunkName,PlatformName,TEXT("CookOpenOrder.txt")); + FFileHelper::SaveStringArrayToFile(IoStoreCommands,*IoStoreCommandsFile); + FString IoStoreCommandlet = FString::Printf( + TEXT("-CreateGlobalContainer=\"%s\" -CookedDirectory=\"%s\" -Commands=\"%s\" -CookerOrder=\"%s\" -TargetPlatform=\"%s\" %s %s"), + // *UFlibHotPatcherCoreHelper::GetUECmdBinary(), + // *UFlibPatchParserHelper::GetProjectFilePath(), + *PlatformGlocalContainers, + *PlatformCookDir, + *IoStoreCommandsFile, + *CookOrderFile, + *PlatformName, + *IoStoreCommandletOptions, + *OutputDirectoryCmd + ); + FString IoStoreNativeCommandlet = FString::Printf( + TEXT("\"%s\" \"%s\" -run=IoStore %s"), + *UFlibHotPatcherCoreHelper::GetUECmdBinary(), + *UFlibPatchParserHelper::GetProjectFilePath(), + *IoStoreCommandlet + ); + + // FCommandLine::Set(*IoStoreCommandlet); + UE_LOG(LogHotPatcher,Log,TEXT("%s"),*IoStoreNativeCommandlet); + // CreateIoStoreContainerFiles(*IoStoreCommandlet); + + TSharedPtr IoStoreProc = MakeShareable( + new FProcWorkerThread( + TEXT("PakIoStoreThread"), + UFlibHotPatcherCoreHelper::GetUECmdBinary(), + FString::Printf(TEXT("\"%s\" -run=IoStore %s"), *UFlibPatchParserHelper::GetProjectFilePath(),*IoStoreCommandlet) + ) + ); + IoStoreProc->ProcOutputMsgDelegate.BindStatic(&::ReceiveOutputMsg); + IoStoreProc->Execute(); + IoStoreProc->Join(); + } + } + } +#endif + return true; + }; + + // setup 11 save difference to file + bool SaveDifferenceWorker(FHotPatcherPatchContext& Context) + { + SCOPED_NAMED_EVENT_TEXT("SaveDifferenceWorker",FColor::Red); + if(Context.GetPakFileNum()) + { + TimeRecorder SaveDiffTR(FString::Printf(TEXT("Save Patch Diff info"))); + FText DiaLogMsg = FText::Format(NSLOCTEXT("ExportPatch", "ExportPatchDiffFile", "Generating Diff info of version {0}"), FText::FromString(Context.CurrentVersion.VersionId)); + + auto SavePatchDiffJsonLambda = [&Context](const FHotPatcherVersion& InSaveVersion, const FPatchVersionDiff& InDiff)->bool + { + bool bStatus = false; + for(ETargetPlatform Platform:Context.GetSettingObject()->PakTargetPlatforms) + { + FString PlatformName = THotPatcherTemplateHelper::GetEnumNameByValue(Platform); + if (Context.GetSettingObject()->IsSaveDiffAnalysis()) + { + auto SerializeChangedAssetInfo = [](const FPatchVersionDiff& InAssetInfo)->FString + { + FString AddAssets; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(InAssetInfo,AddAssets); + return AddAssets; + }; + + FString SerializeDiffInfo = SerializeChangedAssetInfo(InDiff); + + // FString::Printf(TEXT("%s"),*SerializeDiffInfo); + + FString SaveDiffToFile = FPaths::Combine( + // Context.GetSettingObject()->GetCurrentVersionSavePath(), + Context.GetSettingObject()->GetChunkSavedDir(InSaveVersion.VersionId,InSaveVersion.BaseVersionId,TEXT(""),PlatformName), + FString::Printf(TEXT("%s_%s_Diff.json"), *InSaveVersion.BaseVersionId, *InSaveVersion.VersionId) + ); + if (UFlibAssetManageHelper::SaveStringToFile(SaveDiffToFile, SerializeDiffInfo)) + { + bStatus = true; + + FString Msg = FString::Printf(TEXT("Succeed to export New Patch Diff Info."),*SaveDiffToFile); + Context.OnPaking.Broadcast(TEXT("SavePatchDiffInfo"),Msg); + } + } + } + return bStatus; + }; + + SavePatchDiffJsonLambda(Context.CurrentVersion, Context.VersionDiff); + + if(::IsRunningCommandlet()) + { + Context.OnPaking.Broadcast(TEXT("ExportPatchDiffFile"),*DiaLogMsg.ToString()); + + } + else + { + Context.UnrealPakSlowTask->EnterProgressFrame(1.0, DiaLogMsg); + } + } + return true; + }; + + // setup 12 + bool SaveNewReleaseWorker(FHotPatcherPatchContext& Context) + { + SCOPED_NAMED_EVENT_TEXT("SaveNewReleaseWorker",FColor::Red); + if(!Context.GetSettingObject()->IsStorageNewRelease()) + return true; + // save Patch Tracked asset info to file + if(Context.GetPakFileNum()) + { + TimeRecorder TR(FString::Printf(TEXT("Save New Release info"))); + FText DiaLogMsg = FText::Format(NSLOCTEXT("ExportPatch", "ExportPatchAssetInfo", "Generating Patch Tacked Asset info of version {0}"), FText::FromString(Context.CurrentVersion.VersionId)); + if(::IsRunningCommandlet()) + { + FString Msg = FString::Printf(TEXT("Generating Patch Tacked Asset info of version %s."),*Context.CurrentVersion.VersionId); + Context.OnPaking.Broadcast(TEXT("ExportPatchAssetInfo"),DiaLogMsg.ToString()); + } + else + { + Context.UnrealPakSlowTask->EnterProgressFrame(1.0, DiaLogMsg); + } + + FString SerializeReleaseVersionInfo; + Context.NewReleaseVersion = UFlibHotPatcherCoreHelper::MakeNewReleaseByDiff(Context.BaseVersion, Context.VersionDiff,Context.GetSettingObject()); + THotPatcherTemplateHelper::TSerializeStructAsJsonString(Context.NewReleaseVersion, SerializeReleaseVersionInfo); + + FString SaveCurrentVersionToFile = FPaths::Combine( + Context.GetSettingObject()->GetCurrentVersionSavePath(), + FString::Printf(TEXT("%s_Release.json"), *Context.CurrentVersion.VersionId) + ); + if (UFlibAssetManageHelper::SaveStringToFile(SaveCurrentVersionToFile, SerializeReleaseVersionInfo)) + { + if(::IsRunningCommandlet()) + { + FString Msg = FString::Printf(TEXT("Export NewRelease to %s."),*SaveCurrentVersionToFile); + Context.OnPaking.Broadcast(TEXT("SavePatchDiffInfo"),Msg); + }else + { + auto Msg = LOCTEXT("SavePatchDiffInfo", "Export NewRelease Successfuly."); + FHotPatcherDelegates::Get().GetNotifyFileGenerated().Broadcast(Msg, SaveCurrentVersionToFile); + } + } + } + return true; + }; + + // setup 13 serialize all pak file info + bool SavePakFileInfoWorker(FHotPatcherPatchContext& Context) + { + SCOPED_NAMED_EVENT_TEXT("SavePakFileInfoWorker",FColor::Red); + const FPatherResult& PatherResult = Context.PatchProxy->GetPatcherResult(); + FString PakResultPath = FPaths::Combine( + Context.GetSettingObject()->GetCurrentVersionSavePath(), + FString::Printf(TEXT("%s_PakResults.json"),*Context.CurrentVersion.VersionId) + ); + ExportStructToFile(Context,PatherResult,PakResultPath,true,TEXT("PakResults")); + + if(!Context.GetSettingObject()->IsStoragePakFileInfo()) + return true; + if(Context.GetSettingObject()) + { + TimeRecorder TR(FString::Printf(TEXT("Save All Pak file info"))); + FText DiaLogMsg = FText::Format(NSLOCTEXT("ExportPatch", "ExportPatchPakFileInfo", "Generating All Platform Pak info of version {0}"), FText::FromString(Context.CurrentVersion.VersionId)); + if(::IsRunningCommandlet()) + { + Context.OnPaking.Broadcast(TEXT("ExportPatchPakFileInfo"),*DiaLogMsg.ToString()); + } + else + { + Context.UnrealPakSlowTask->EnterProgressFrame(1.0,DiaLogMsg); + } + FString PakFilesInfoStr; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(Context.PakFilesInfoMap, PakFilesInfoStr); + + if (!PakFilesInfoStr.IsEmpty()) + { + FString SavePakFilesPath = FPaths::Combine( + Context.GetSettingObject()->GetCurrentVersionSavePath(), + FString::Printf(TEXT("%s_PakFilesInfo.json"), *Context.CurrentVersion.VersionId) + ); + if (UFlibAssetManageHelper::SaveStringToFile(SavePakFilesPath, PakFilesInfoStr) && FPaths::FileExists(SavePakFilesPath)) + { + if(::IsRunningCommandlet()) + { + FString Msg = FString::Printf(TEXT("Export PakFileInfo to %s."),*SavePakFilesPath); + Context.OnPaking.Broadcast(TEXT("SavedPakFileMsg"),Msg); + }else + { + FText Msg = LOCTEXT("SavedPakFileMsg_ExportSuccessed", "Export PakFileInfo Successfuly."); + FHotPatcherDelegates::Get().GetNotifyFileGenerated().Broadcast(Msg, SavePakFilesPath); + } + } + } + } + return true; + }; + + // setup 14 serialize patch config + bool SavePatchConfigWorker(FHotPatcherPatchContext& Context) + { + SCOPED_NAMED_EVENT_TEXT("SavePatchConfigWorker",FColor::Red); + + TimeRecorder TR(FString::Printf(TEXT("Save patch config"))); + FText DiaLogMsg = FText::Format(NSLOCTEXT("ExportPatch", "ExportPatchConfig", "Generating Current Patch Config of version {0}"), FText::FromString(Context.CurrentVersion.VersionId)); + if(::IsRunningCommandlet()) + { + Context.OnPaking.Broadcast(TEXT("ExportPatchConfig"),*DiaLogMsg.ToString()); + } + else + { + Context.UnrealPakSlowTask->EnterProgressFrame(1.0, DiaLogMsg); + } + if (Context.GetSettingObject()->IsSaveConfig()) + { + TArray PakTargetPlatforms = Context.GetSettingObject()->GetPakTargetPlatforms(); + for(ETargetPlatform Platform:PakTargetPlatforms) + { + FString PlatformName = THotPatcherTemplateHelper::GetEnumNameByValue(Platform); + FString SavedBaseDir = Context.GetSettingObject()->GetChunkSavedDir(Context.CurrentVersion.VersionId,Context.CurrentVersion.BaseVersionId,Context.GetSettingObject()->GetVersionId(),PlatformName); + FString SaveConfigPath = FPaths::Combine( + SavedBaseDir, + FString::Printf(TEXT("%s_%s_PatchConfig.json"),*PlatformName,*Context.CurrentVersion.VersionId) + ); + FExportPatchSettings TempPatchSettings = *Context.GetSettingObject(); + TempPatchSettings.PakTargetPlatforms.Empty(); + TempPatchSettings.PakTargetPlatforms.Add(Platform); + ExportStructToFile(Context,TempPatchSettings,SaveConfigPath,false,TEXT("PatchConfig")); + } + if(PakTargetPlatforms.Num()) + { + FString SaveConfigPath = FPaths::Combine( + Context.GetSettingObject()->GetCurrentVersionSavePath(), + FString::Printf(TEXT("%s_PatchConfig.json"),*Context.CurrentVersion.VersionId) + ); + ExportStructToFile(Context,*Context.GetSettingObject(),SaveConfigPath,true,TEXT("PatchConfig")); + } + } + + return true; + }; + + // setup 15 + // bool BackupMetadataWorker(FHotPatcherPatchContext& Context) + // { + // SCOPED_NAMED_EVENT_TEXT("BackupMetadataWorker",FColor::Red); + // // backup Metadata + // if(Context.GetPakFileNum()) + // { + // TimeRecorder TR(FString::Printf(TEXT("Backup Metadata"))); + // FText DiaLogMsg = FText::Format(NSLOCTEXT("BackupMetadata", "BackupMetadata", "Backup Release {0} Metadatas."), FText::FromString(Context.GetSettingObject()->GetVersionId())); + // Context.UnrealPakSlowTask->EnterProgressFrame(1.0, DiaLogMsg); + // if(Context.GetSettingObject()->IsBackupMetadata()) + // { + // UFlibHotPatcherCoreHelper::BackupMetadataDir( + // FPaths::ProjectDir(), + // FApp::GetProjectName(), + // Context.GetSettingObject()->GetPakTargetPlatforms(), + // FPaths::Combine(Context.GetSettingObject()->GetSaveAbsPath(), + // Context.GetSettingObject()->GetVersionId()) + // ); + // } + // } + // return true; + // }; + + // setup 16 + bool ShowSummaryWorker(FHotPatcherPatchContext& Context) + { + SCOPED_NAMED_EVENT_TEXT("ShowSummaryWorker",FColor::Red); + // show summary infomation + if(Context.GetPakFileNum()) + { + Context.OnShowMsg.Broadcast(UFlibHotPatcherCoreHelper::PatchSummary(Context.VersionDiff)); + Context.OnShowMsg.Broadcast(UFlibHotPatcherCoreHelper::ReleaseSummary(Context.NewReleaseVersion)); + } + return true; + }; + + // setup 17 + bool OnFaildDispatchWorker(FHotPatcherPatchContext& Context) + { + SCOPED_NAMED_EVENT_TEXT("OnFaildDispatchWorker",FColor::Red); + if (!Context.GetPakFileNum()) + { + UE_LOG(LogHotPatcher, Warning, TEXT("The Patch not contain any invalie file!")); + Context.OnShowMsg.Broadcast(TEXT("The Patch not contain any invalie file!")); + } + else + { + Context.OnShowMsg.Broadcast(TEXT("")); + } + return true; + }; + +}; + +#undef LOCTEXT_NAMESPACE diff --git a/HotPatcher/Source/HotPatcherCore/Private/CreatePatch/ReleaseProxy.cpp b/HotPatcher/Source/HotPatcherCore/Private/CreatePatch/ReleaseProxy.cpp new file mode 100644 index 0000000..f821a8c --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Private/CreatePatch/ReleaseProxy.cpp @@ -0,0 +1,242 @@ +#include "CreatePatch/ReleaseProxy.h" +#include "CreatePatch/ScopedSlowTaskContext.h" +#include "FHotPatcherVersion.h" +#include "FlibPatchParserHelper.h" +#include "HotPatcherLog.h" + +// engine header +#include "FlibHotPatcherCoreHelper.h" +#include "Misc/DateTime.h" +#include "CoreGlobals.h" +#include "Logging/LogMacros.h" +#include "CreatePatch/HotPatcherContext.h" + +#define LOCTEXT_NAMESPACE "UExportRelease" + +namespace ReleaseWorker +{ + // inport from paklist + bool ImportPakListWorker(FHotPatcherReleaseContext& Context); + bool ImportProjectSettingsWorker(FHotPatcherReleaseContext& Context); + // scan new release + bool ExportNewReleaseWorker(FHotPatcherReleaseContext& Context); + // save release asset info + bool SaveReleaseVersionWorker(FHotPatcherReleaseContext& Context); + bool SaveReleaseConfigWorker(FHotPatcherReleaseContext& Context); + // backup Metadata + bool BakcupMetadataWorker(FHotPatcherReleaseContext& Context); + // backup config + bool BakcupProjectConfigWorker(FHotPatcherReleaseContext& Context); + // display release summary + bool ReleaseSummaryWorker(FHotPatcherReleaseContext& Context); + +}; + +bool UReleaseProxy::DoExport() +{ + // TimeRecorder TotalTimeTR(TEXT("Generate the release total time")); + GetSettingObject()->Init(); + bool bRet = true; + FHotPatcherReleaseContext ReleaseContext; + ReleaseContext.ContextSetting = GetSettingObject(); + ReleaseContext.UnrealPakSlowTask = NewObject(); + ReleaseContext.UnrealPakSlowTask->AddToRoot(); + ReleaseContext.Init(); + ReleaseContext.ReleaseProxy = this; + TArray> ReleaseWorker; + ReleaseWorker.Emplace(&::ReleaseWorker::SaveReleaseConfigWorker); + ReleaseWorker.Emplace(&::ReleaseWorker::ImportPakListWorker); + ReleaseWorker.Emplace(&::ReleaseWorker::ImportProjectSettingsWorker); + ReleaseWorker.Emplace(&::ReleaseWorker::ExportNewReleaseWorker); + ReleaseWorker.Emplace(&::ReleaseWorker::SaveReleaseVersionWorker); + ReleaseWorker.Emplace(&::ReleaseWorker::BakcupMetadataWorker); + ReleaseWorker.Emplace(&::ReleaseWorker::BakcupProjectConfigWorker); + ReleaseWorker.Emplace(&::ReleaseWorker::ReleaseSummaryWorker); + ReleaseContext.UnrealPakSlowTask->init((float)ReleaseWorker.Num()); + + for(TFunction Worker:ReleaseWorker) + { + if(Worker(ReleaseContext)) + { + continue; + } + else + { + bRet = false; + break; + } + } + ReleaseContext.UnrealPakSlowTask->Final(); + ReleaseContext.Shurdown(); + return bRet; +} + +namespace ReleaseWorker +{ + bool ImportPakListWorker(FHotPatcherReleaseContext& Context) + { + FText DiaLogMsg = NSLOCTEXT("ImportPakListWorker", "ImportPakListWorker", "Import Pak List."); + Context.UnrealPakSlowTask->EnterProgressFrame(1.0, DiaLogMsg); + TimeRecorder TotalTimeTR(TEXT("Import Paklist")); + if(Context.GetSettingObject()->ByPakList) + { + if(Context.GetSettingObject()->PlatformsPakListFiles.Num()) + { + Context.GetSettingObject()->ImportPakLists(); + } + } + return true; + } + + bool ImportProjectSettingsWorker(FHotPatcherReleaseContext& Context) + { + if(Context.GetSettingObject()->IsImportProjectSettings()) + { + UFlibHotPatcherCoreHelper::ImportProjectSettingsToScannerConfig(Context.GetSettingObject()->GetAssetScanConfigRef()); + + ETargetPlatform AddProjectDirToTarget = ETargetPlatform::AllPlatforms; + + TSet AllConfigPlatformSet; + for(const auto& PlatformsPakListFile:Context.GetSettingObject()->PlatformsPakListFiles) + { + AllConfigPlatformSet.Add(PlatformsPakListFile.TargetPlatform); + } + if(AllConfigPlatformSet.Num()) + { + AddProjectDirToTarget = AllConfigPlatformSet.Num() > 1 ? ETargetPlatform::AllPlatforms : AllConfigPlatformSet.Array()[0]; + } + + UFlibHotPatcherCoreHelper::ImportProjectNotAssetDir(Context.GetSettingObject()->GetAddExternAssetsToPlatform(),AddProjectDirToTarget); + } + + return true; + } + + bool ExportNewReleaseWorker(FHotPatcherReleaseContext& Context) + { + TimeRecorder TotalTimeTR(TEXT("Export Release Version Info")); + TArray AllSkipContent; + if(Context.GetSettingObject()->IsForceSkipContent()) + { + AllSkipContent = Context.GetSettingObject()->GetAllSkipContents(); + } + FText DiaLogMsg = FText::Format(NSLOCTEXT("AnalysisRelease", "AnalysisReleaseVersionInfo", "Analysis Release {0} Assets info."), FText::FromString(Context.GetSettingObject()->GetVersionId())); + Context.UnrealPakSlowTask->EnterProgressFrame(1.0, DiaLogMsg); + { + Context.NewReleaseVersion.VersionId = Context.GetSettingObject()->GetVersionId(); + Context.NewReleaseVersion.Date = FDateTime::UtcNow().ToString(); + Context.NewReleaseVersion.BaseVersionId = TEXT(""); + } + UFlibPatchParserHelper::RunAssetScanner(Context.GetSettingObject()->GetAssetScanConfig(),Context.NewReleaseVersion); + UFlibPatchParserHelper::ExportExternAssetsToPlatform(Context.GetSettingObject()->GetAddExternAssetsToPlatform(),Context.NewReleaseVersion,true,Context.GetSettingObject()->GetHashCalculator()); + return true; + } + + // save release asset info + bool SaveReleaseVersionWorker(FHotPatcherReleaseContext& Context) + { + TimeRecorder TR(TEXT("Save new release version info")); + FText DiaLogMsg = FText::Format(NSLOCTEXT("ExportReleaseJson", "ExportReleaseVersionInfoJson", "Export Release {0} Assets info to file."), FText::FromString(Context.GetSettingObject()->GetVersionId())); + Context.UnrealPakSlowTask->EnterProgressFrame(1.0, DiaLogMsg); + FString SaveToJson; + if (THotPatcherTemplateHelper::TSerializeStructAsJsonString(Context.NewReleaseVersion, SaveToJson)) + { + + FString SaveToFile = FPaths::Combine( + FPaths::Combine(Context.GetSettingObject()->GetSaveAbsPath(), Context.GetSettingObject()->GetVersionId()), + FString::Printf(TEXT("%s_Release.json"), *Context.GetSettingObject()->GetVersionId()) + ); + bool runState = UFlibAssetManageHelper::SaveStringToFile(SaveToFile, SaveToJson); + if (runState) + { + auto Message = LOCTEXT("ExportReleaseSuccessNotification", "Succeed to export HotPatcher Release Version."); + if(IsRunningCommandlet()) + { + Context.OnShowMsg.Broadcast(Message.ToString()); + } + else + { + FHotPatcherDelegates::Get().GetNotifyFileGenerated().Broadcast(Message, SaveToFile); + } + + } + UE_LOG(LogHotPatcher, Log, TEXT("HotPatcher Export RELEASE is %s."), runState ? TEXT("Success") : TEXT("FAILD")); + } + return true; + } + + bool SaveReleaseConfigWorker(FHotPatcherReleaseContext& Context) + { + TimeRecorder TR(TEXT("Save new release config")); + FText DiaLogMsg = FText::Format(NSLOCTEXT("ExportReleaseConfig", "ExportReleaseConfigJson", "Export Release {0} Configuration to file."), FText::FromString(Context.GetSettingObject()->GetVersionId())); + Context.UnrealPakSlowTask->EnterProgressFrame(1.0, DiaLogMsg); + FString ConfigJson; + if (THotPatcherTemplateHelper::TSerializeStructAsJsonString(*Context.GetSettingObject(),ConfigJson)) + { + FString SaveToFile = FPaths::Combine( + FPaths::Combine(Context.GetSettingObject()->GetSaveAbsPath(), Context.GetSettingObject()->GetVersionId()), + FString::Printf(TEXT("%s_ReleaseConfig.json"), *Context.GetSettingObject()->GetVersionId()) + ); + bool runState = UFlibAssetManageHelper::SaveStringToFile(SaveToFile, ConfigJson); + if (runState) + { + auto Message = LOCTEXT("ExportReleaseConfigSuccessNotification", "Succeed to export HotPatcher Release Config."); + if(::IsRunningCommandlet()) + { + Context.OnShowMsg.Broadcast(Message.ToString()); + } + else + { + FHotPatcherDelegates::Get().GetNotifyFileGenerated().Broadcast(Message, SaveToFile); + } + } + UE_LOG(LogHotPatcher, Log, TEXT("HotPatcher Export RELEASE CONFIG is %s."), runState ? TEXT("Success") : TEXT("FAILD")); + } + return true; + } + + // backup Metadata + bool BakcupMetadataWorker(FHotPatcherReleaseContext& Context) + { + TimeRecorder TR(TEXT("Backup Metadata")); + FText DiaLogMsg = FText::Format(NSLOCTEXT("BackupMetadata", "BackupMetadata", "Backup Release {0} Metadatas."), FText::FromString(Context.GetSettingObject()->GetVersionId())); + Context.UnrealPakSlowTask->EnterProgressFrame(1.0, DiaLogMsg); + if(Context.GetSettingObject()->IsBackupMetadata()) + { + UFlibHotPatcherCoreHelper::BackupMetadataDir( + FPaths::ProjectDir(), + FApp::GetProjectName(), + Context.GetSettingObject()->GetBackupMetadataPlatforms(), + FPaths::Combine(Context.GetSettingObject()->GetSaveAbsPath(), + Context.GetSettingObject()->GetVersionId()) + ); + } + return true; + } + // backup project config + bool BakcupProjectConfigWorker(FHotPatcherReleaseContext& Context) + { + TimeRecorder TR(TEXT("Backup Config")); + FText DiaLogMsg = FText::Format(NSLOCTEXT("BackupProjectConfig", "BackupProjectConfig", "Backup Release {0} Configs."), FText::FromString(Context.GetSettingObject()->GetVersionId())); + Context.UnrealPakSlowTask->EnterProgressFrame(1.0, DiaLogMsg); + if(Context.GetSettingObject()->IsBackupProjectConfig()) + { + UFlibHotPatcherCoreHelper::BackupProjectConfigDir( + FPaths::ProjectDir(), + FPaths::Combine(Context.GetSettingObject()->GetSaveAbsPath(),Context.GetSettingObject()->GetVersionId()) + ); + } + return true; + } + + bool ReleaseSummaryWorker(FHotPatcherReleaseContext& Context) + { + FText DiaLogMsg = NSLOCTEXT("ReleaseSummaryWorker", "ReleaseSummaryWorker", "Release Summary."); + Context.UnrealPakSlowTask->EnterProgressFrame(1.0, DiaLogMsg); + TimeRecorder TR(TEXT("Generate Release Summary")); + Context.OnShowMsg.Broadcast(UFlibHotPatcherCoreHelper::ReleaseSummary(Context.NewReleaseVersion)); + return true; + } + +}; +#undef LOCTEXT_NAMESPACE diff --git a/HotPatcher/Source/HotPatcherCore/Private/FCountServerlessWrapper.cpp b/HotPatcher/Source/HotPatcherCore/Private/FCountServerlessWrapper.cpp new file mode 100644 index 0000000..0c656c0 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Private/FCountServerlessWrapper.cpp @@ -0,0 +1,196 @@ +#pragma once +#include "FCountServerlessWrapper.h" + +#include "Misc/App.h" +#include "Misc/Base64.h" +#include "HttpModule.h" +#include "SocketSubsystem.h" +#include "Interfaces/IHttpRequest.h" +#include "Interfaces/IHttpResponse.h" +#include "Serialization/JsonReader.h" +#include "Serialization/JsonSerializer.h" +#include "Resources/Version.h" +#include "CoreGlobals.h" +#include "HttpManager.h" + +FProjectVersionDesc FCountServerlessWrapper::MakeCurrentProject() +{ + FProjectVersionDesc result; + result.ProjectName = FApp::GetProjectName(); + result.GameName = GConfig->GetStr(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"),TEXT("ApplicationDisplayName"),GEngineIni); + result.EngineVersion = FString::Printf(TEXT("%d.%d.%d"),ENGINE_MAJOR_VERSION,ENGINE_MINOR_VERSION,ENGINE_PATCH_VERSION); + + static FString HostName; + if(HostName.IsEmpty()) + { + ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->GetHostName(HostName); + } + result.UserName = HostName; + return result; +} + +#define COUNTER_API_HOST TEXT("https://appcounter.imzlp.com/1.1/classes/HotPatcher") +#define COUNTER_APPID TEXT("Y1diamRWR3pkRlVWcEtNSWkwMFFGdGVxLWd6R3pvSHN6") +#define COUNTER_KEY TEXT("M1VrUldPaHhsRThGcnBKSUZpNlUxNExI") + +FServerRequestInfo FCountServerlessWrapper::MakeServerRequestInfo() +{ + FServerRequestInfo Info; + Info.Host = COUNTER_API_HOST; + Info.Key = COUNTER_KEY; + Info.AppId = COUNTER_APPID; + return Info; +} + +void CancelRequest(FHttpRequestPtr& Request) +{ + if(FHttpModule::Get().GetHttpManager().IsValidRequest(Request.Get())) + { + Request->CancelRequest(); + } +} + +FCountServerlessWrapper::~FCountServerlessWrapper() +{ + CancelRequest(ObjectIDRequest); + CancelRequest(ToServerRequest); +} + +void FCountServerlessWrapper::Processor() +{ + if(!UE_BUILD_SHIPPING) + { + RequestObjectID(); + } +} + +void FCountServerlessWrapper::RequestObjectID() +{ + CancelRequest(ObjectIDRequest); + FHttpModule::Get().SetHttpTimeout(5.0); + ObjectIDRequest = FHttpModule::Get().CreateRequest(); + ObjectIDRequest->OnProcessRequestComplete().BindRaw(this, &FCountServerlessWrapper::OnObjectIdReceived); + ObjectIDRequest->SetURL(RequestInfo.Host); + ObjectIDRequest->SetHeader(TEXT("X-LC-Id"),Decode(RequestInfo.AppId)); + ObjectIDRequest->SetHeader(TEXT("X-LC-Key"),Decode(RequestInfo.Key)); + ObjectIDRequest->SetHeader(TEXT("Content-Type"),TEXT("application/json")); + ObjectIDRequest->SetVerb(TEXT("GET")); + ObjectIDRequest->ProcessRequest(); +} + +void FCountServerlessWrapper::OnObjectIdReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) +{ + TMap NameIdMaps; + int32 Count = 0; + if(bSuccess) + { + FString ResponseStr = Response->GetContentAsString(); + TSharedRef> JsonReader = TJsonReaderFactory::Create(ResponseStr); + TSharedPtr DeserializeJsonObject; + if (FJsonSerializer::Deserialize(JsonReader, DeserializeJsonObject)) + { + const TArray< TSharedPtr>* JsonValues; + bool bDeserialize = DeserializeJsonObject->TryGetArrayField(TEXT("results"),JsonValues); + if(bDeserialize) + { + for(TSharedPtr JsonValue:*JsonValues) + { + auto ChildJsonValue =JsonValue->AsObject(); + FString ProjectName = ChildJsonValue->GetStringField(TEXT("ProjectName")); + FString objectId = ChildJsonValue->GetStringField(TEXT("objectId")); + if(!NameIdMaps.Contains(ProjectName)) + { + Count++; + } + NameIdMaps.Add(ProjectName,objectId); + } + } + } + if(NameIdMaps.Contains(FApp::GetProjectName())) + { + ToServerRequest = UpdateToServer(Desc,*NameIdMaps.Find(FApp::GetProjectName())); + } + else if(!!Count) + { + ToServerRequest = CreateToServer(Desc); + } + } +} + +FHttpRequestPtr FCountServerlessWrapper::UpdateToServer(const FProjectVersionDesc& InDesc,const FString& ObjectID) +{ + FString ContentJsonStr = FString::Printf( + TEXT("{\"ProjectName\": \"%s\",\"GameName\":\"%s\",\"EngineVersion\":\"%s\",\"PluginVersion\":\"%s\",\"UserName\":\"%s\",%s}"), + *InDesc.ProjectName, + *InDesc.GameName, + *InDesc.EngineVersion, + *InDesc.PluginVersion, + *InDesc.UserName, + TEXT("\"Count\":{\"__op\":\"Increment\",\"amount\":1}") + ); + FHttpRequestPtr CreateToServerRequest = FHttpModule::Get().CreateRequest(); + FString UpdateObjectURL = FString::Printf(TEXT("%s/%s"),*RequestInfo.Host,*ObjectID); + CreateToServerRequest->SetURL(UpdateObjectURL); + CreateToServerRequest->SetHeader(TEXT("X-LC-Id"),Decode(RequestInfo.AppId)); + CreateToServerRequest->SetHeader(TEXT("X-LC-Key"),Decode(RequestInfo.Key)); + CreateToServerRequest->SetHeader(TEXT("Content-Type"),TEXT("application/json")); + CreateToServerRequest->SetContentAsString(ContentJsonStr); + CreateToServerRequest->SetVerb(TEXT("PUT")); + CreateToServerRequest->OnProcessRequestComplete().BindRaw(this, &FCountServerlessWrapper::OnUpdateToServerReceived); + CreateToServerRequest->ProcessRequest(); + return CreateToServerRequest; +} + +void FCountServerlessWrapper::OnUpdateToServerReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, + bool bSuccess) +{ + if(bSuccess) + { + FString ResponseStr = Response->GetContentAsString(); + } +} + +FHttpRequestPtr FCountServerlessWrapper::CreateToServer(const FProjectVersionDesc& InDesc) +{ + FString ContentJsonStr = FString::Printf( + TEXT("{\"ProjectName\": \"%s\",\"GameName\":\"%s\",\"EngineVersion\":\"%s\",\"PluginVersion\":\"%s\",\"UserName\":\"%s\",\"Count\":%d}"), + *InDesc.ProjectName, + *InDesc.GameName, + *InDesc.EngineVersion, + *InDesc.PluginVersion, + *InDesc.UserName, + 1 + ); + + FHttpRequestPtr UpdateToServerRequest = FHttpModule::Get().CreateRequest(); + UpdateToServerRequest->SetURL(RequestInfo.Host); + UpdateToServerRequest->SetHeader(TEXT("X-LC-Id"),Decode(RequestInfo.AppId)); + UpdateToServerRequest->SetHeader(TEXT("X-LC-Key"),Decode(RequestInfo.Key)); + UpdateToServerRequest->SetHeader(TEXT("Content-Type"),TEXT("application/json")); + UpdateToServerRequest->SetContentAsString(ContentJsonStr); + UpdateToServerRequest->SetVerb(TEXT("POST")); + UpdateToServerRequest->OnProcessRequestComplete().BindRaw(this, &FCountServerlessWrapper::CreateToServerReceived); + UpdateToServerRequest->ProcessRequest(); + return UpdateToServerRequest; +} + +void FCountServerlessWrapper::CreateToServerReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess) +{ + if(bSuccess) + { + FString ResponseStr = Response->GetContentAsString(); + } +} + +FString FCountServerlessWrapper::Decode(const FString& Encode) +{ + FString DecodeStr; + if(FBase64::Decode(Encode,DecodeStr)) + { + return DecodeStr; + } + else + { + return Encode; + } +} diff --git a/HotPatcher/Source/HotPatcherCore/Private/FlibHotPatcherCoreHelper.cpp b/HotPatcher/Source/HotPatcherCore/Private/FlibHotPatcherCoreHelper.cpp new file mode 100644 index 0000000..e82bc11 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Private/FlibHotPatcherCoreHelper.cpp @@ -0,0 +1,2718 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "FlibHotPatcherCoreHelper.h" +#include "HotPatcherLog.h" +#include "HotPatcherCore.h" +#include "CreatePatch/FExportPatchSettings.h" +#include "CreatePatch/FExportReleaseSettings.h" +#include "ShaderLibUtils/FlibShaderCodeLibraryHelper.h" +#include "ThreadUtils/FThreadUtils.hpp" + +// engine header +#include "Misc/ConfigCacheIni.h" +#include "Editor.h" +#include "Interfaces/ITargetPlatform.h" +#include "HAL/PlatformFilemanager.h" +#include "Interfaces/ITargetPlatformManagerModule.h" +#include "GameDelegates.h" +#include "GameMapsSettings.h" +#include "INetworkFileSystemModule.h" +#include "IPlatformFileSandboxWrapper.h" +#include "PackageHelperFunctions.h" +#include "Async/Async.h" +#include "Engine/AssetManager.h" +#include "Interfaces/IPluginManager.h" +#include "Misc/RedirectCollector.h" +#include "Misc/SecureHash.h" +#include "Serialization/ArrayWriter.h" +#include "Settings/ProjectPackagingSettings.h" +#include "ShaderCompiler.h" +#include "Async/ParallelFor.h" +#include "CreatePatch/PatcherProxy.h" +#include "Materials/MaterialInstance.h" +#include "Materials/MaterialInstanceConstant.h" +#include "ProfilingDebugging/LoadTimeTracker.h" +#include "UObject/ConstructorHelpers.h" +#include "Misc/EngineVersionComparison.h" +#include "Misc/CoreMisc.h" +#include "DerivedDataCacheInterface.h" + + +DEFINE_LOG_CATEGORY(LogHotPatcherCoreHelper); + +TArray UFlibHotPatcherCoreHelper::GetAllCookOption() +{ + TArray result + { + "Iterate", + "UnVersioned", + "CookAll", + "Compressed" + }; + return result; +} + +void UFlibHotPatcherCoreHelper::CheckInvalidCookFilesByAssetDependenciesInfo( + const FString& InProjectAbsDir, + const FString& OverrideCookedDir, + const FString& InPlatformName, + const FAssetDependenciesInfo& InAssetDependencies, + TArray& OutValidAssets, + TArray& OutInvalidAssets) +{ + OutValidAssets.Empty(); + OutInvalidAssets.Empty(); + const TArray& AllAssetDetails = InAssetDependencies.GetAssetDetails(); + + for (const auto& AssetDetail : AllAssetDetails) + { + TArray CookedAssetPath; + TArray CookedAssetRelativePath; + FString AssetLongPackageName = UFlibAssetManageHelper::PackagePathToLongPackageName(AssetDetail.PackagePath.ToString()); + + FAssetData CurrentAssetData; + UFlibAssetManageHelper::GetSingleAssetsData(AssetDetail.PackagePath.ToString(),CurrentAssetData); + + // if(!CurrentAssetData.GetAsset()->IsValidLowLevelFast()) + // { + // UE_LOG(LogHotPatcherCoreHelper,Warning,TEXT("%s is invalid Asset Uobject"),*CurrentAssetData.PackageName.ToString()); + // continue; + // } + if ((CurrentAssetData.PackageFlags & PKG_EditorOnly)!=0) + { + UE_LOG(LogHotPatcherCoreHelper,Warning,TEXT("Miss %s it's EditorOnly Assets!"),*CurrentAssetData.PackageName.ToString()); + continue; + } + + if (UFlibAssetManageHelper::ConvLongPackageNameToCookedPath( + InProjectAbsDir, + InPlatformName, + AssetLongPackageName, + CookedAssetPath, + CookedAssetRelativePath, + OverrideCookedDir + )) + { + if (CookedAssetPath.Num() > 0) + { + OutValidAssets.Add(AssetDetail); + } + else + { + OutInvalidAssets.Add(AssetDetail); + } + } + } +} + + +#include "Kismet/KismetSystemLibrary.h" + +FChunkInfo UFlibHotPatcherCoreHelper::MakeChunkFromPatchSettings(const FExportPatchSettings* InPatchSetting) +{ + FChunkInfo Chunk; + if (!(InPatchSetting && InPatchSetting)) + { + return Chunk; + } + auto GetThis = [&]()->FExportPatchSettings*{return const_cast(InPatchSetting);}; + Chunk.ChunkName = InPatchSetting->VersionId; + Chunk.bMonolithic = false; + Chunk.MonolithicPathMode = EMonolithicPathMode::MountPath; + Chunk.bStorageUnrealPakList = InPatchSetting->GetUnrealPakSettings().bStoragePakList; + Chunk.bStorageIoStorePakList = InPatchSetting->GetIoStoreSettings().bStoragePakList; + Chunk.bOutputDebugInfo = Chunk.bStorageIoStorePakList || Chunk.bStorageUnrealPakList; + Chunk.AssetIncludeFilters = GetThis()->GetAssetIncludeFilters(); + Chunk.AssetIgnoreFilters = GetThis()->GetAssetIgnoreFilters(); + Chunk.bAnalysisFilterDependencies = InPatchSetting->IsAnalysisFilterDependencies(); + Chunk.IncludeSpecifyAssets = GetThis()->GetIncludeSpecifyAssets(); + Chunk.bForceSkipContent = GetThis()->IsForceSkipContent(); + Chunk.ForceSkipClasses = GetThis()->GetAssetScanConfig().ForceSkipClasses; + Chunk.ForceSkipContentRules = GetThis()->GetForceSkipContentRules(); + Chunk.ForceSkipAssets = GetThis()->GetForceSkipAssets(); + Chunk.AddExternAssetsToPlatform = GetThis()->GetAddExternAssetsToPlatform(); + Chunk.AssetRegistryDependencyTypes = InPatchSetting->GetAssetRegistryDependencyTypes(); + Chunk.InternalFiles.bIncludeAssetRegistry = InPatchSetting->IsIncludeAssetRegistry(); + Chunk.InternalFiles.bIncludeGlobalShaderCache = InPatchSetting->IsIncludeGlobalShaderCache(); + Chunk.InternalFiles.bIncludeShaderBytecode = InPatchSetting->IsIncludeShaderBytecode(); + Chunk.InternalFiles.bIncludeEngineIni = InPatchSetting->IsIncludeEngineIni(); + Chunk.InternalFiles.bIncludePluginIni = InPatchSetting->IsIncludePluginIni(); + Chunk.InternalFiles.bIncludeProjectIni = InPatchSetting->IsIncludeProjectIni(); + Chunk.CookShaderOptions = InPatchSetting->GetCookShaderOptions(); + return Chunk; +} + +FChunkInfo UFlibHotPatcherCoreHelper::MakeChunkFromPatchVerison(const FHotPatcherVersion& InPatchVersion) +{ + FChunkInfo Chunk; + Chunk.ChunkName = InPatchVersion.VersionId; + Chunk.bMonolithic = false; + Chunk.bStorageUnrealPakList = false; + auto ConvPathStrToDirPaths = [](const TArray& InPathsStr)->TArray + { + TArray result; + for (const auto& Dir : InPathsStr) + { + FDirectoryPath Path; + Path.Path = Dir; + result.Add(Path); + } + return result; + }; + + //Chunk.AssetIncludeFilters = ConvPathStrToDirPaths(InPatchVersion.IgnoreFilter); + // Chunk.AssetIgnoreFilters = ConvPathStrToDirPaths(InPatchVersion.IgnoreFilter); + Chunk.bAnalysisFilterDependencies = false; + const TArray& AllVersionAssets = InPatchVersion.AssetInfo.GetAssetDetails(); + + for (const auto& Asset : AllVersionAssets) + { + FPatcherSpecifyAsset CurrentAsset; + CurrentAsset.Asset = FSoftObjectPath(Asset.PackagePath.ToString()); + CurrentAsset.bAnalysisAssetDependencies = false; + Chunk.IncludeSpecifyAssets.AddUnique(CurrentAsset); + } + // Chunk.AddExternDirectoryToPak = InPatchSetting->GetAddExternDirectory(); + // for (const auto& File : InPatchVersion.ExternalFiles) + // { + // Chunk.AddExternFileToPak.AddUnique(File.Value); + // } + + TArray VersionPlatforms; + + InPatchVersion.PlatformAssets.GetKeys(VersionPlatforms); + + for(auto Platform:VersionPlatforms) + { + Chunk.AddExternAssetsToPlatform.Add(InPatchVersion.PlatformAssets[Platform]); + } + + Chunk.InternalFiles.bIncludeAssetRegistry = false; + Chunk.InternalFiles.bIncludeGlobalShaderCache = false; + Chunk.InternalFiles.bIncludeShaderBytecode = false; + Chunk.InternalFiles.bIncludeEngineIni = false; + Chunk.InternalFiles.bIncludePluginIni = false; + Chunk.InternalFiles.bIncludeProjectIni = false; + + return Chunk; +} + +#define REMAPPED_PLUGGINS TEXT("RemappedPlugins") + + +FString ConvertToFullSandboxPath( const FString &FileName, bool bForWrite ) +{ + FString Result; + static FString EngineAbsDir = FPaths::ConvertRelativePathToFull(FPaths::EngineContentDir()); + static FString ProjectContentAbsir = FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir()); + + if(FileName.StartsWith(ProjectContentAbsir)) + { + FString GameFileName = FileName; + GameFileName.RemoveFromStart(ProjectContentAbsir); + Result = FPaths::Combine(FApp::GetProjectName(),TEXT("Content"),GameFileName); + return Result; + } + if(FileName.StartsWith(EngineAbsDir)) + { + FString EngineFileName = FileName; + EngineFileName.RemoveFromStart(EngineAbsDir); + Result = FPaths::Combine(TEXT("Engine/Content"),EngineFileName); + return Result; + } + TArray > PluginsToRemap = IPluginManager::Get().GetEnabledPlugins(); + // Ideally this would be in the Sandbox File but it can't access the project or plugin + if (PluginsToRemap.Num() > 0) + { + // Handle remapping of plugins + for (TSharedRef Plugin : PluginsToRemap) + { + FString PluginContentDir; + if (FPaths::IsRelative(FileName)) + PluginContentDir = Plugin->GetContentDir(); + else + PluginContentDir = FPaths::ConvertRelativePathToFull(Plugin->GetContentDir()); + // UE_LOG(LogHotPatcherCoreHelper,Log,TEXT("Plugin Content:%s"),*PluginContentDir); + if (FileName.StartsWith(PluginContentDir)) + { + FString LoadingFrom; + FString BasePath; + switch(Plugin->GetLoadedFrom()) + { + case EPluginLoadedFrom::Engine: + { + BasePath = FPaths::ConvertRelativePathToFull(FPaths::EngineDir());//TEXT("Engine/Plugins"); + LoadingFrom = TEXT("Engine"); + break; + } + case EPluginLoadedFrom::Project: + { + BasePath = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir());//FPaths::Combine(FApp::GetProjectName(),TEXT("Plugins")); + LoadingFrom = FApp::GetProjectName(); + break; + } + } + FString AssetAbsPath = FPaths::ConvertRelativePathToFull(FileName); + if(AssetAbsPath.StartsWith(BasePath)) + { + Result = LoadingFrom / AssetAbsPath.RightChop(BasePath.Len()); + } + } + } + } + + return Result; +} + +FString UFlibHotPatcherCoreHelper::GetAssetCookedSavePath(const FString& BaseDir, const FString PacakgeName, const FString& Platform) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibHotPatcherCoreHelper::GetCookAssetsSaveDir",FColor::Red); + FString CookDir; + FString Filename; + // FString PackageFilename; + FString StandardFilename; + FName StandardFileFName = NAME_None; + PRAGMA_DISABLE_DEPRECATION_WARNINGS + if (FPackageName::DoesPackageExist(PacakgeName,NULL, &Filename, false)) + { + StandardFilename = FPaths::ConvertRelativePathToFull(Filename); + FString SandboxFilename = ConvertToFullSandboxPath(*StandardFilename, true); + CookDir = FPaths::Combine(BaseDir,Platform,SandboxFilename); + } + PRAGMA_ENABLE_DEPRECATION_WARNINGS + + return CookDir; +} + +FString UFlibHotPatcherCoreHelper::GetProjectCookedDir() +{ + return FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectSavedDir(),TEXT("Cooked")));; +} + +#if WITH_PACKAGE_CONTEXT +// engine header +#include "UObject/SavePackage.h" + +#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION > 25 +#include "Serialization/BulkDataManifest.h" +#endif + +#if ENGINE_MAJOR_VERSION > 4 /*&& ENGINE_MINOR_VERSION > 0 */ +#include "ZenStoreWriter.h" +#include "Cooker/PackageWriter/HotPatcherPackageWriter.h" +#endif + + + +FSavePackageContext* UFlibHotPatcherCoreHelper::CreateSaveContext(const ITargetPlatform* TargetPlatform, + bool bUseZenLoader, + const FString& OverrideCookedDir + ) +{ + FSavePackageContext* SavePackageContext = NULL; +#if WITH_PACKAGE_CONTEXT + const FString PlatformString = TargetPlatform->PlatformName(); + + // const FString ResolvedRootPath = RootPathSandbox.Replace(TEXT("[Platform]"), *PlatformString); + const FString ResolvedProjectPath = FPaths::Combine(OverrideCookedDir,FString::Printf(TEXT("%s/%s"),*TargetPlatform->PlatformName(),FApp::GetProjectName())); + const FString ResolvedMetadataPath = FPaths::Combine(ResolvedProjectPath,TEXT("Mededata")); + + FConfigFile PlatformEngineIni; + FConfigCacheIni::LoadLocalIniFile(PlatformEngineIni, TEXT("Engine"), true, *TargetPlatform->IniPlatformName()); + + +#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION > 25 + FPackageStoreBulkDataManifest* BulkDataManifest = new FPackageStoreBulkDataManifest(ResolvedProjectPath); + FLooseFileWriter* LooseFileWriter = bUseZenLoader ? new FLooseFileWriter() : nullptr; + bool bLegacyBulkDataOffsets = false; + PlatformEngineIni.GetBool(TEXT("Core.System"), TEXT("LegacyBulkDataOffsets"), bLegacyBulkDataOffsets); + SavePackageContext = new FSavePackageContext(LooseFileWriter, BulkDataManifest, bLegacyBulkDataOffsets); +#endif + +#if ENGINE_MAJOR_VERSION > 4 /*&& ENGINE_MINOR_VERSION > 0*/ + ICookedPackageWriter* PackageWriter = nullptr; + FString WriterDebugName; + if (bUseZenLoader) + { + PackageWriter = new FZenStoreWriter(ResolvedProjectPath, ResolvedMetadataPath, TargetPlatform); + WriterDebugName = TEXT("ZenStore"); + } + else + { + // FAsyncIODelete AsyncIODelete{ResolvedProjectPath}; + PackageWriter = new FHotPatcherPackageWriter;// new FLooseCookedPackageWriter(ResolvedProjectPath, ResolvedMetadataPath, TargetPlatform,AsyncIODelete,FPackageNameCache{},IPluginManager::Get().GetEnabledPlugins()); + WriterDebugName = TEXT("DirectoryWriter"); + } + + SavePackageContext = new FSavePackageContext(TargetPlatform, PackageWriter); +#endif +#endif + return SavePackageContext; +} + + +TMap> UFlibHotPatcherCoreHelper::CreatePlatformsPackageContexts( + const TArray& Platforms,bool bIoStore,const FString& OverrideCookedDir) + +{ + SCOPED_NAMED_EVENT_TEXT("CreatePlatformsPackageContexts",FColor::Red); + + TMap> PlatformSavePackageContexts; + TArray PlatformNames; + + for(ETargetPlatform Platform:Platforms) + { + PlatformNames.AddUnique(THotPatcherTemplateHelper::GetEnumNameByValue(Platform)); + } + + ITargetPlatformManagerModule& TPM = GetTargetPlatformManagerRef(); + const TArray& TargetPlatforms = TPM.GetTargetPlatforms(); + TArray CookPlatforms; + const FString ProjectPath = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir()); + for (ITargetPlatform *TargetPlatform : TargetPlatforms) + { + if (PlatformNames.Contains(TargetPlatform->PlatformName())) + { + CookPlatforms.AddUnique(TargetPlatform); + const FString PlatformString = TargetPlatform->PlatformName(); + + ETargetPlatform Platform; + THotPatcherTemplateHelper::GetEnumValueByName(TargetPlatform->PlatformName(),Platform); + PlatformSavePackageContexts.Add(Platform,MakeShareable(UFlibHotPatcherCoreHelper::CreateSaveContext(TargetPlatform,bIoStore,OverrideCookedDir))); + } + } + return PlatformSavePackageContexts; +} + + +bool UFlibHotPatcherCoreHelper::SavePlatformBulkDataManifest(TMap>&PlatformSavePackageContexts,ETargetPlatform Platform) +{ + SCOPED_NAMED_EVENT_TEXT("SavePlatformBulkDataManifest",FColor::Red); + bool bRet = false; + if(!PlatformSavePackageContexts.Contains(Platform)) + return bRet; + TSharedPtr PackageContext = *PlatformSavePackageContexts.Find(Platform); +#if ENGINE_MAJOR_VERSION < 5 && ENGINE_MINOR_VERSION > 25 + if (PackageContext != nullptr && PackageContext->BulkDataManifest != nullptr) + { + PackageContext->BulkDataManifest->Save(); + bRet = true; + } +#endif + return bRet; +} +#endif + +void UFlibHotPatcherCoreHelper::CookAssets( + const TArray& Assets, + const TArray& Platforms, + FCookActionCallback CookActionCallback, +#if WITH_PACKAGE_CONTEXT + class TMap PlatformSavePackageContext, +#endif + const FString& InSavePath +) +{ + SCOPED_NAMED_EVENT_TEXT("CookAssets",FColor::Red); + TArray StringPlatforms; +#if WITH_PACKAGE_CONTEXT + TMap FinalPlatformSavePackageContext; +#endif + TMap CookPlatforms; + for(const auto& Platform:Platforms) + { + FString PlatformName = THotPatcherTemplateHelper::GetEnumNameByValue(Platform); + StringPlatforms.AddUnique(PlatformName); +#if WITH_PACKAGE_CONTEXT + FSavePackageContext* CurrentPackageContext = NULL; + if(PlatformSavePackageContext.Contains(Platform)) + { + CurrentPackageContext = *PlatformSavePackageContext.Find(Platform); + } + + FinalPlatformSavePackageContext.Add(PlatformName,CurrentPackageContext); +#endif + ITargetPlatform* PlatformIns = GetPlatformByName(PlatformName); + if(PlatformIns) + { + CookPlatforms.Add(Platform,PlatformIns); + } + } + + for(int32 index = 0; index < Assets.Num();++index) + { + UE_LOG(LogHotPatcherCoreHelper,Display,TEXT("%d packages is cooked,Remain %d Total %d"), index, Assets.Num() - index, Assets.Num()); + CookPackage(Assets[index],CookPlatforms,CookActionCallback, +#if WITH_PACKAGE_CONTEXT + FinalPlatformSavePackageContext, +#endif + InSavePath,false); + } + // UFlibShaderCodeLibraryHelper::WaitShaderCompilingComplete(); + // UFlibHotPatcherCoreHelper::WaitForAsyncFileWrites(); +} + +struct FFilterEditorOnlyFlag +{ + FFilterEditorOnlyFlag(UPackage* InPackage,ITargetPlatform* InPlatform) + { + Package = InPackage; + Platform = InPlatform; + if(!Platform->HasEditorOnlyData()) + { + Package->SetPackageFlags(PKG_FilterEditorOnly); + } + else + { + Package->ClearPackageFlags(PKG_FilterEditorOnly); + } + } + ~FFilterEditorOnlyFlag() + { + if(!Platform->HasEditorOnlyData()) + { + Package->ClearPackageFlags(PKG_FilterEditorOnly); + } + } + UPackage* Package; + ITargetPlatform* Platform; +}; +#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION > 25 +UE_TRACE_EVENT_BEGIN(CUSTOM_LOADTIMER_LOG, CookPackage, NoSync) + UE_TRACE_EVENT_FIELD(Trace::WideString, PackageName) +UE_TRACE_EVENT_END() +#endif + +bool UFlibHotPatcherCoreHelper::CookPackage( + const FSoftObjectPath& AssetObjectPath, + TMap CookPlatforms, + FCookActionCallback CookActionCallback, +#if WITH_PACKAGE_CONTEXT + class TMap PlatformSavePackageContext, +#endif + const FString& InSavePath, + bool bStorageConcurrent +) +{ + SCOPED_NAMED_EVENT_TEXT("CookPackageForObjectPath",FColor::Red); + UPackage* Package = UFlibAssetManageHelper::GetPackage(*AssetObjectPath.GetLongPackageName()); + return UFlibHotPatcherCoreHelper::CookPackage(Package,CookPlatforms,CookActionCallback, +#if WITH_PACKAGE_CONTEXT + PlatformSavePackageContext, +#endif + InSavePath,bStorageConcurrent); +} + + +bool UFlibHotPatcherCoreHelper::CookPackage( + UPackage* Package, + TMap CookPlatforms, + FCookActionCallback CookActionCallback, +#if WITH_PACKAGE_CONTEXT + class TMap PlatformSavePackageContext, +#endif + const FString& InSavePath, + bool bStorageConcurrent +) +{ + FString LongPackageName = UFlibAssetManageHelper::LongPackageNameToPackagePath(Package->GetPathName()); + TMap PlatformSavePaths; + for(auto Platform: CookPlatforms) + { + FString SavePath = UFlibHotPatcherCoreHelper::GetAssetCookedSavePath(InSavePath,LongPackageName, Platform.Value->PlatformName()); + PlatformSavePaths.Add(*Platform.Value->PlatformName(),SavePath); + } + + return UFlibHotPatcherCoreHelper::CookPackage(Package,CookPlatforms,CookActionCallback, +#if WITH_PACKAGE_CONTEXT + PlatformSavePackageContext, +#endif + PlatformSavePaths,bStorageConcurrent); +} + +bool UFlibHotPatcherCoreHelper::CookPackage( + UPackage* Package, + TMap CookPlatforms, + FCookActionCallback CookActionCallback, +#if WITH_PACKAGE_CONTEXT + class TMap PlatformSavePackageContext, +#endif + const TMap& CookedPlatformSavePaths, + bool bStorageConcurrent +) +{ + bool bSuccessed = false; + + FString LongPackageName = UFlibAssetManageHelper::LongPackageNameToPackagePath(Package->GetPathName()); + FString FakePackageName = FString(TEXT("Package ")) + LongPackageName; + +#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION > 25 + SCOPED_CUSTOM_LOADTIMER(CookPackage) + ADD_CUSTOM_LOADTIMER_META(CookPackage, PackageName, *FakePackageName); +#else + SCOPED_NAMED_EVENT_TEXT("CookPackage",FColor::Red); +#endif + { +#if ENGINE_MINOR_VERSION < 26 + FScopedNamedEvent CookPackageEvent(FColor::Red,*FString::Printf(TEXT("%s"),*LongPackageName)); +#endif + // UPackage* Package = UFlibAssetManageHelper::GetPackage(FName(LongPackageName)); + + bool bIsFailedPackage = !Package || Package->HasAnyPackageFlags(PKG_EditorOnly); + if(!Package) + { + UE_LOG(LogHotPatcher,Warning,TEXT("Cook %s UPackage is null!"),*LongPackageName); + return false; + } + if(Package && Package->HasAnyPackageFlags(PKG_EditorOnly)) + { + UE_LOG(LogHotPatcher,Warning,TEXT("Cook %s Failed! It is EditorOnly Package!"),*LongPackageName); + } + + + bool bUnversioned = true; + bool CookLinkerDiff = false; + uint32 SaveFlags = UFlibHotPatcherCoreHelper::GetCookSaveFlag(Package,bUnversioned,bStorageConcurrent,CookLinkerDiff); + EObjectFlags CookedFlags = UFlibHotPatcherCoreHelper::GetObjectFlagForCooked(Package); + + + #if ENGINE_MAJOR_VERSION > 4 + FName PackageFileName = Package->GetLoadedPath().GetPackageFName(); + #else + FName PackageFileName = Package->FileName; + #endif + if(PackageFileName.IsNone() && LongPackageName.IsEmpty()) + return bSuccessed; + + uint32 OriginalPackageFlags = Package->GetPackageFlags(); + + for(auto& Platform:CookPlatforms) + { + // if(bIsFailedPackage) + // { + // CookActionCallback.OnAssetCooked(Package,Platform,ESavePackageResult::Error); + // return false; + // } + + FFilterEditorOnlyFlag SetPackageEditorOnlyFlag(Package,Platform.Value); + + FString PackageName = PackageFileName.IsNone() ? LongPackageName :PackageFileName.ToString(); + FString CookedSavePath = *CookedPlatformSavePaths.Find(*Platform.Value->PlatformName()); + + ETargetPlatform TargetPlatform = Platform.Key; + + /* for material cook + * Illegal call to StaticFindObject() while serializing object data! + * ![](https://img.imzlp.com/imgs/zlp/picgo/2022/202211121451617.png) + */ + if(!bStorageConcurrent) + { + TSet ProcessedObjs; + TSet PendingProcessObjs; + UFlibHotPatcherCoreHelper::CacheForCookedPlatformData(TArray{Package},TArray{Platform.Value},ProcessedObjs,PendingProcessObjs,bStorageConcurrent, false); + } + if(GCookLog) + { + UE_LOG(LogHotPatcher,Log,TEXT("Cook %s for %s"),*Package->GetName(),*Platform.Value->PlatformName()); + } + #if WITH_PACKAGE_CONTEXT + FSavePackageContext* CurrentPlatformPackageContext = nullptr; + if(PlatformSavePackageContext.Contains(Platform.Value->PlatformName())) + { + CurrentPlatformPackageContext = *PlatformSavePackageContext.Find(Platform.Value->PlatformName()); + } + #if ENGINE_MAJOR_VERSION > 4 /*&& ENGINE_MINOR_VERSION > 0*/ + IPackageWriter::FBeginPackageInfo BeginInfo; + BeginInfo.PackageName = Package->GetFName(); + BeginInfo.LooseFilePath = CookedSavePath; + CurrentPlatformPackageContext->PackageWriter->BeginPackage(BeginInfo); + #endif + #endif + + if(CookActionCallback.OnCookBegin) + { + CookActionCallback.OnCookBegin(PackageName,TargetPlatform); + } + + if (!Platform.Value->HasEditorOnlyData()) + { + Package->SetPackageFlags(PKG_FilterEditorOnly); + } + else + { + Package->ClearPackageFlags(PKG_FilterEditorOnly); + } + + PRAGMA_DISABLE_DEPRECATION_WARNINGS + GIsCookerLoadingPackage = true; + PRAGMA_ENABLE_DEPRECATION_WARNINGS + FSavePackageResultStruct Result = GEditor->Save( Package, nullptr, CookedFlags, *CookedSavePath, + GError, nullptr, false, false, SaveFlags, Platform.Value, + FDateTime::MinValue(), false, /*DiffMap*/ nullptr + #if WITH_PACKAGE_CONTEXT + ,CurrentPlatformPackageContext + #endif + ); + GIsCookerLoadingPackage = false; + + bSuccessed = Result == ESavePackageResult::Success; + + if (CookActionCallback.OnAssetCooked) + { + CookActionCallback.OnAssetCooked(Package,TargetPlatform,Result.Result); + } + + #if WITH_PACKAGE_CONTEXT + // in UE5.1 + #if ENGINE_MAJOR_VERSION > 4 /*&& ENGINE_MINOR_VERSION > 0*/ + // save cooked file to desk in UE5-main + if(bSuccessed) + { + //const FAssetPackageData* AssetPackageData = UFlibAssetManageHelper::GetPackageDataByPackageName(Package->GetFName().ToString()); + ICookedPackageWriter::FCommitPackageInfo Info; +#if UE_VERSION_OLDER_THAN(5,1,0) + Info.bSucceeded = bSuccessed; +#else + Info.Status = bSuccessed ? IPackageWriter::ECommitStatus::Success : IPackageWriter::ECommitStatus::Error; +#endif + Info.PackageName = Package->GetFName(); + // PRAGMA_DISABLE_DEPRECATION_WARNINGS + Info.PackageGuid = FGuid::NewGuid(); //AssetPackageData ? AssetPackageData->PackageGuid : FGuid::NewGuid(); + // PRAGMA_ENABLE_DEPRECATION_WARNINGS + // Info.Attachments.Add({ "Dependencies", TargetDomainDependencies }); + // TODO: Reenable BuildDefinitionList once FCbPackage support for empty FCbObjects is in + //Info.Attachments.Add({ "BuildDefinitionList", BuildDefinitionList }); + Info.WriteOptions = IPackageWriter::EWriteOptions::Write; + if (!!(SaveFlags & SAVE_ComputeHash)) + { + Info.WriteOptions |= IPackageWriter::EWriteOptions::ComputeHash; + } + CurrentPlatformPackageContext->PackageWriter->CommitPackage(MoveTemp(Info)); + } + #endif + #endif + } + + Package->SetPackageFlagsTo(OriginalPackageFlags); + } + return bSuccessed; +} + +void UFlibHotPatcherCoreHelper::CookChunkAssets( + TArray Assets, + const TArray& Platforms, + FCookActionCallback CookActionCallback, +#if WITH_PACKAGE_CONTEXT + class TMap PlatformSavePackageContext, +#endif + const FString& InSavePath +) +{ + + TArray SoftObjectPaths; + + for(const auto& Asset:Assets) + { + SoftObjectPaths.Emplace(Asset.PackagePath.ToString()); + } + if(!!SoftObjectPaths.Num()) + { + UFlibHotPatcherCoreHelper::CookAssets(SoftObjectPaths,Platforms,CookActionCallback, +#if WITH_PACKAGE_CONTEXT + PlatformSavePackageContext, +#endif + InSavePath); + } +} + +ITargetPlatform* UFlibHotPatcherCoreHelper::GetTargetPlatformByName(const FString& PlatformName) +{ + ITargetPlatformManagerModule& TPM = GetTargetPlatformManagerRef(); + const TArray& TargetPlatforms = TPM.GetTargetPlatforms(); + ITargetPlatform* PlatformIns = NULL; + for (ITargetPlatform *TargetPlatform : TargetPlatforms) + { + if (PlatformName.Equals(TargetPlatform->PlatformName())) + { + PlatformIns = TargetPlatform; + } + } + return PlatformIns; +} + +TArray UFlibHotPatcherCoreHelper::GetTargetPlatformsByNames(const TArray& Platforms) +{ + + TArray result; + for(const auto& Platform:Platforms) + { + + ITargetPlatform* Found = UFlibHotPatcherCoreHelper::GetTargetPlatformByName(THotPatcherTemplateHelper::GetEnumNameByValue(Platform,false)); + if(Found) + { + result.Add(Found); + } + } + return result; +} + + + +FString UFlibHotPatcherCoreHelper::GetUnrealPakBinary() +{ +#if PLATFORM_WINDOWS + return FPaths::Combine( + FPaths::ConvertRelativePathToFull(FPaths::EngineDir()), + TEXT("Binaries"), +#if PLATFORM_64BITS + TEXT("Win64"), +#else + TEXT("Win32"), +#endif + TEXT("UnrealPak.exe") + ); +#endif + +#if PLATFORM_MAC + return FPaths::Combine( + FPaths::ConvertRelativePathToFull(FPaths::EngineDir()), + TEXT("Binaries"), + TEXT("Mac"), + TEXT("UnrealPak") + ); +#endif + + return TEXT(""); +} + +FString UFlibHotPatcherCoreHelper::GetUECmdBinary() +{ + FString Binary; +#if ENGINE_MAJOR_VERSION > 4 + Binary = TEXT("UnrealEditor"); +#else + Binary = TEXT("UE4Editor"); +#endif + + FString ConfigutationName = ANSI_TO_TCHAR(COMPILER_CONFIGURATION_NAME); + bool bIsDevelopment = ConfigutationName.Equals(TEXT("Development")); + +#if PLATFORM_WINDOWS + FString PlatformName; + #if PLATFORM_64BITS + PlatformName = TEXT("Win64"); + #else + PlatformName = TEXT("Win32"); + #endif + + return FPaths::Combine( + FPaths::ConvertRelativePathToFull(FPaths::EngineDir()), + TEXT("Binaries"),PlatformName,FString::Printf(TEXT("%s%s-Cmd.exe"),*Binary,bIsDevelopment ? TEXT("") : *FString::Printf(TEXT("-%s-%s"),*PlatformName,*ConfigutationName))); +#endif + +#if PLATFORM_MAC +#if ENGINE_MAJOR_VERSION < 5 && ENGINE_MINOR_VERSION <= 21 + return FPaths::Combine( + FPaths::ConvertRelativePathToFull(FPaths::EngineDir()), + TEXT("Binaries"),TEXT("Mac"),TEXT("UE4Editor.app/Contents/MacOS"), + FString::Printf(TEXT("%s%s"),*Binary, + bIsDevelopment ? TEXT("") : *FString::Printf(TEXT("-Mac-%s"),*ConfigutationName))); +#else + return FPaths::Combine( + FPaths::ConvertRelativePathToFull(FPaths::EngineDir()), + TEXT("Binaries"),TEXT("Mac"), + FString::Printf(TEXT("%s%s-Cmd"),*Binary, + bIsDevelopment ? TEXT("") : *FString::Printf(TEXT("-Mac-%s"),*ConfigutationName))); +#endif +#endif + return TEXT(""); +} + + +FProcHandle UFlibHotPatcherCoreHelper::DoUnrealPak(TArray UnrealPakCommandletOptions, bool block) +{ + FString UnrealPakBinary = UFlibHotPatcherCoreHelper::GetUnrealPakBinary(); + + FString CommandLine; + for (const auto& Option : UnrealPakCommandletOptions) + { + CommandLine.Append(FString::Printf(TEXT(" %s"), *Option)); + } + + // create UnrealPak process + + uint32 *ProcessID = NULL; + FProcHandle ProcHandle = FPlatformProcess::CreateProc(*UnrealPakBinary, *CommandLine, true, false, false, ProcessID, 1, NULL, NULL, NULL); + + if (ProcHandle.IsValid()) + { + if (block) + { + FPlatformProcess::WaitForProc(ProcHandle); + } + } + return ProcHandle; +} + +FString UFlibHotPatcherCoreHelper::GetMetadataDir(const FString& ProjectDir, const FString& ProjectName,ETargetPlatform Platform) +{ + FString result; + FString PlatformName = THotPatcherTemplateHelper::GetEnumNameByValue(Platform,false); + return FPaths::Combine(ProjectDir,TEXT("Saved/Cooked"),PlatformName,ProjectName,TEXT("Metadata")); +} + +void UFlibHotPatcherCoreHelper::CleanDefaultMetadataCache(const TArray& TargetPlatforms) +{ + SCOPED_NAMED_EVENT_TEXT("CleanDefaultMetadataCache",FColor::Red); + for(ETargetPlatform Platform:TargetPlatforms) + { + FString MetadataDir = GetMetadataDir(FPaths::ProjectDir(),FApp::GetProjectName(),Platform); + if(FPaths::DirectoryExists(MetadataDir)) + { + IFileManager::Get().DeleteDirectory(*MetadataDir,false,true); + } + } +} + +void UFlibHotPatcherCoreHelper::BackupMetadataDir(const FString& ProjectDir, const FString& ProjectName, + const TArray& Platforms, const FString& OutDir) +{ + IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); + for(const auto& Platform:Platforms) + { + FString MetadataDir = FPaths::ConvertRelativePathToFull(UFlibHotPatcherCoreHelper::GetMetadataDir(ProjectDir,ProjectName,Platform)); + FString OutMetadir = FPaths::Combine(OutDir,TEXT("BackupMetadatas"),THotPatcherTemplateHelper::GetEnumNameByValue(Platform,false)); + if(FPaths::DirectoryExists(MetadataDir)) + { + PlatformFile.CreateDirectoryTree(*OutMetadir); + PlatformFile.CopyDirectoryTree(*OutMetadir,*MetadataDir,true); + } + } +} + +void UFlibHotPatcherCoreHelper::BackupProjectConfigDir(const FString& ProjectDir,const FString& OutDir) +{ + IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); + FString ConfigDir = FPaths::ConvertRelativePathToFull(FPaths::ProjectConfigDir()); + FString OutConfigdir = FPaths::Combine(OutDir,TEXT("Config")); + if(FPaths::DirectoryExists(ConfigDir)) + { + PlatformFile.CreateDirectoryTree(*OutConfigdir); + PlatformFile.CopyDirectoryTree(*OutConfigdir,*ConfigDir,true); + } +} + +FString UFlibHotPatcherCoreHelper::ReleaseSummary(const FHotPatcherVersion& NewVersion) +{ + FString result = TEXT("\n---------------------HotPatcher Release Summary---------------------\n"); + + auto ParserPlatformExternAssets = [](ETargetPlatform Platform,const FPlatformExternAssets& PlatformExternAssets)->FString + { + FString PlatformName = THotPatcherTemplateHelper::GetEnumNameByValue(Platform);; + FString result = FString::Printf(TEXT("======== %s Non-Assets =========\n"),*PlatformName); + uint32 BeginLength = result.Len(); + result += FString::Printf(TEXT("External Files: %d\n"),PlatformExternAssets.AddExternFileToPak.Num()); + // result += FString::Printf(TEXT("External Directorys: %d\n"),PlatformExternAssets.AddExternDirectoryToPak.Num()); + + for(uint32 index=0;index AddModuleAssetNumMap; + result += FString::Printf(TEXT("New Release Asset Number: %d\n"),UFlibAssetManageHelper::ParserAssetDependenciesInfoNumber(NewVersion.AssetInfo,AddModuleAssetNumMap)); + result += UFlibAssetManageHelper::ParserModuleAssetsNumMap(AddModuleAssetNumMap); + TArray Keys; + NewVersion.PlatformAssets.GetKeys(Keys); + for(const auto& Key:Keys) + { + result += ParserPlatformExternAssets(Key,*NewVersion.PlatformAssets.Find(Key)); + } + return result; +} + +FString UFlibHotPatcherCoreHelper::PatchSummary(const FPatchVersionDiff& DiffInfo) +{ + auto ExternFileSummary = [](ETargetPlatform Platform,const FPatchVersionExternDiff& ExternDiff)->FString + { + FString PlatformName = THotPatcherTemplateHelper::GetEnumNameByValue(Platform);; + FString result = FString::Printf(TEXT("======== %s Non-Assets =========\n"),*PlatformName); + uint32 BeginLength = result.Len(); + result += FString::Printf(TEXT("Add Non-Asset Number: %d\n"),ExternDiff.AddExternalFiles.Num()); + result += FString::Printf(TEXT("Modify Non-Asset Number: %d\n"),ExternDiff.ModifyExternalFiles.Num()); + result += FString::Printf(TEXT("Delete Non-Asset Number: %d\n"),ExternDiff.DeleteExternalFiles.Num()); + for(uint32 index=0;index AddModuleAssetNumMap; + result += FString::Printf(TEXT("Add Asset Number: %d\n"),UFlibAssetManageHelper::ParserAssetDependenciesInfoNumber(DiffInfo.AssetDiffInfo.AddAssetDependInfo,AddModuleAssetNumMap)); + result += UFlibAssetManageHelper::ParserModuleAssetsNumMap(AddModuleAssetNumMap); + TMap ModifyModuleAssetNumMap; + result += FString::Printf(TEXT("Modify Asset Number: %d\n"),UFlibAssetManageHelper::ParserAssetDependenciesInfoNumber(DiffInfo.AssetDiffInfo.ModifyAssetDependInfo,ModifyModuleAssetNumMap)); + result += UFlibAssetManageHelper::ParserModuleAssetsNumMap(ModifyModuleAssetNumMap); + TMap DeleteModuleAssetNumMap; + result += FString::Printf(TEXT("Delete Asset Number: %d\n"),UFlibAssetManageHelper::ParserAssetDependenciesInfoNumber(DiffInfo.AssetDiffInfo.DeleteAssetDependInfo,DeleteModuleAssetNumMap)); + result += UFlibAssetManageHelper::ParserModuleAssetsNumMap(DeleteModuleAssetNumMap); + TArray Platforms; + DiffInfo.PlatformExternDiffInfo.GetKeys(Platforms); + for(const auto& Platform:Platforms) + { + result += ExternFileSummary(Platform,*DiffInfo.PlatformExternDiffInfo.Find(Platform)); + } + return result; +} + +FString UFlibHotPatcherCoreHelper::ReplacePakRegular(const FReplacePakRegular& RegularConf, const FString& InRegular) +{ + return UFlibPatchParserHelper::ReplacePakRegular(RegularConf,InRegular); +} + +bool UFlibHotPatcherCoreHelper::CheckSelectedAssetsCookStatus(const FString& OverrideCookedDir,const TArray& PlatformNames, const FAssetDependenciesInfo& SelectedAssets, FString& OutMsg) +{ + OutMsg.Empty(); + // 检查所修改的资源是否被Cook过 + for (const auto& PlatformName : PlatformNames) + { + TArray ValidCookAssets; + TArray InvalidCookAssets; + + UFlibHotPatcherCoreHelper::CheckInvalidCookFilesByAssetDependenciesInfo(UKismetSystemLibrary::GetProjectDirectory(),OverrideCookedDir, PlatformName, SelectedAssets, ValidCookAssets, InvalidCookAssets); + + if (InvalidCookAssets.Num() > 0) + { + OutMsg.Append(FString::Printf(TEXT("%s UnCooked Assets:\n"), *PlatformName)); + + for (const auto& Asset : InvalidCookAssets) + { + FString AssetLongPackageName = UFlibAssetManageHelper::PackagePathToLongPackageName(Asset.PackagePath.ToString()); + OutMsg.Append(FString::Printf(TEXT("\t%s\n"), *AssetLongPackageName)); + } + } + } + + return OutMsg.IsEmpty(); +} + +bool UFlibHotPatcherCoreHelper::CheckPatchRequire(const FString& OverrideCookedDir,const FPatchVersionDiff& InDiff,const TArray& PlatformNames,FString& OutMsg) +{ + bool Status = false; + // 错误处理 + { + FString GenErrorMsg; + FAssetDependenciesInfo AllChangedAssetInfo = UFlibAssetManageHelper::CombineAssetDependencies(InDiff.AssetDiffInfo.AddAssetDependInfo, InDiff.AssetDiffInfo.ModifyAssetDependInfo); + bool bSelectedCookStatus = CheckSelectedAssetsCookStatus(OverrideCookedDir,PlatformNames, AllChangedAssetInfo, GenErrorMsg); + + // 如果有错误信息 则输出后退出 + if (!bSelectedCookStatus) + { + OutMsg = GenErrorMsg; + Status = false; + } + else + { + OutMsg = TEXT(""); + Status = true; + } + } + return Status; +} + +FString UFlibHotPatcherCoreHelper::Conv2IniPlatform(const FString& Platform) +{ + FString Result; + static TMap PlatformMaps; + static bool bInit = false; + if(!bInit) + { + ITargetPlatformManagerModule& TPM = GetTargetPlatformManagerRef(); + const TArray& TargetPlatforms = TPM.GetTargetPlatforms(); + TArray CookPlatforms; + for (ITargetPlatform *TargetPlatform : TargetPlatforms) + { + PlatformMaps.Add(TargetPlatform->PlatformName(),TargetPlatform->IniPlatformName()); + } + bInit = true; + } + + if(PlatformMaps.Contains(Platform)) + { + Result = *PlatformMaps.Find(Platform); + } + return Result; +} + +TArray UFlibHotPatcherCoreHelper::GetSupportPlatforms() +{ + TArray Result; + ITargetPlatformManagerModule& TPM = GetTargetPlatformManagerRef(); + const TArray& TargetPlatforms = TPM.GetTargetPlatforms(); + TArray CookPlatforms; + for (ITargetPlatform *TargetPlatform : TargetPlatforms) + { + Result.AddUnique(TargetPlatform->PlatformName()); + } + return Result; +} + +#define ENCRYPT_CRYPTO_NAME TEXT("cryptokeys") + +FString UFlibHotPatcherCoreHelper::GetEncryptSettingsCommandlineOptions(const FPakEncryptSettings& PakEncryptSettings,const FString& PlatformName) +{ + FString Result; + + FString CryptoKey = UFlibPatchParserHelper::ReplaceMarkPath(PakEncryptSettings.CryptoKeys.FilePath); + + auto AppendCommandOptions = [&Result](bool bEncryptIndex,bool bbEncrypt,bool bSign) + { + if(bbEncrypt) + { + Result += TEXT("-encrypt "); + } + if(bEncryptIndex) + { + Result += TEXT("-encryptindex "); + } + if(bSign) + { + Result += TEXT("-sign "); + } + }; + + FEncryptSetting EncryptSettings = UFlibPatchParserHelper::GetCryptoSettingByPakEncryptSettings(PakEncryptSettings); + if(PakEncryptSettings.bUseDefaultCryptoIni) + { + FString SaveTo = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectSavedDir(),TEXT("HotPatcher"),TEXT("Crypto.json"))); + FPakEncryptionKeys PakEncryptionKeys = UFlibPatchParserHelper::GetCryptoByProjectSettings(); + UFlibPatchParserHelper::SerializePakEncryptionKeyToFile(PakEncryptionKeys,SaveTo); + CryptoKey = SaveTo; + } + + AppendCommandOptions( + EncryptSettings.bEncryptIndex, + (EncryptSettings.bEncryptIniFiles || EncryptSettings.bEncryptAllAssetFiles || EncryptSettings.bEncryptUAssetFiles), + EncryptSettings.bSign + ); + + if(FPaths::FileExists(CryptoKey)) + { + Result += FString::Printf(TEXT("-%s=\"%s\" "),ENCRYPT_CRYPTO_NAME,*CryptoKey); + } + + Result += FString::Printf(TEXT("-projectdir=\"%s\" "),*FPaths::ConvertRelativePathToFull(FPaths::ProjectDir())); + Result += FString::Printf(TEXT("-enginedir=\"%s\" "),*FPaths::ConvertRelativePathToFull(FPaths::EngineDir())); + Result += FString::Printf(TEXT("-platform=%s"),*PlatformName); + return Result; +} + +ITargetPlatform* UFlibHotPatcherCoreHelper::GetPlatformByName(const FString& Name) +{ + static TMap PlatformNameMap; + + if(PlatformNameMap.Contains(Name)) + return *PlatformNameMap.Find(Name); + + ITargetPlatformManagerModule& TPM = GetTargetPlatformManagerRef(); + const TArray& TargetPlatforms = TPM.GetTargetPlatforms(); + ITargetPlatform* result = NULL; + for (ITargetPlatform *TargetPlatform : TargetPlatforms) + { + if (Name.Equals(TargetPlatform->PlatformName())) + { + result = TargetPlatform; + PlatformNameMap.Add(Name,TargetPlatform); + break; + } + } + return result; +} + +#include "BaseWidgetBlueprint.h" +#include "Blueprint/UserWidget.h" +#include "Blueprint/WidgetTree.h" +#include "WidgetBlueprint.h" + +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; + + UFlibPatchParserHelper::DiffVersionAssets( + CurrentVersionAssetDependInfo, + BaseVersionAssetDependInfo, + VersionDiffInfo.AssetDiffInfo.AddAssetDependInfo, + VersionDiffInfo.AssetDiffInfo.ModifyAssetDependInfo, + VersionDiffInfo.AssetDiffInfo.DeleteAssetDependInfo + ); + + UFlibPatchParserHelper::DiffVersionAllPlatformExFiles(PatchSetting,Base,New,VersionDiffInfo.PlatformExternDiffInfo); + + if(PatchSetting.GetIgnoreDeletionModulesAsset().Num()) + { + for(const auto& ModuleName:PatchSetting.GetIgnoreDeletionModulesAsset()) + { + VersionDiffInfo.AssetDiffInfo.DeleteAssetDependInfo.AssetsDependenciesMap.Remove(ModuleName); + } + } + + if(PatchSetting.IsRecursiveWidgetTree()) + { + AnalysisWidgetTree(Base,New,VersionDiffInfo); + } + if(PatchSetting.IsAnalysisMatInstance()) + { + AnalysisMaterialInstance(Base,New,VersionDiffInfo); + } + + if(PatchSetting.IsForceSkipContent()) + { + TArray AllSkipDirContents = UFlibAssetManageHelper::DirectoriesToStrings(PatchSetting.GetForceSkipContentRules()); + UFlibPatchParserHelper::ExcludeContentForVersionDiff(VersionDiffInfo,AllSkipDirContents,EHotPatcherMatchModEx::StartWith); + TArray AllSkipAssets = UFlibAssetManageHelper::SoftObjectPathsToStrings(PatchSetting.GetForceSkipAssets()); + UFlibPatchParserHelper::ExcludeContentForVersionDiff(VersionDiffInfo,AllSkipAssets,EHotPatcherMatchModEx::Equal); + } + // clean deleted asset info in patch + if(PatchSetting.IsIgnoreDeletedAssetsInfo()) + { + UE_LOG(LogHotPatcher,Display,TEXT("ignore deleted assets info in patch...")); + VersionDiffInfo.AssetDiffInfo.DeleteAssetDependInfo.AssetsDependenciesMap.Empty(); + if(VersionDiffInfo.PlatformExternDiffInfo.Contains(ETargetPlatform::AllPlatforms)) + { + VersionDiffInfo.PlatformExternDiffInfo.Find(ETargetPlatform::AllPlatforms)->DeleteExternalFiles.Empty(); + } + for(const auto& Platform:PatchSetting.GetPakTargetPlatforms()) + { + VersionDiffInfo.PlatformExternDiffInfo.Find(Platform)->DeleteExternalFiles.Empty(); + } + } + + return VersionDiffInfo; +} + + +void UFlibHotPatcherCoreHelper::AnalysisWidgetTree(const FHotPatcherVersion& Base, const FHotPatcherVersion& New,FPatchVersionDiff& PakDiff,int32 flags) +{ + UClass* WidgetBlueprintClass = UWidgetBlueprint::StaticClass(); + UFlibHotPatcherCoreHelper::AnalysisDependenciesAssets(Base,New,PakDiff,WidgetBlueprintClass,TArray{WidgetBlueprintClass},true); +} + +void UFlibHotPatcherCoreHelper::AnalysisMaterialInstance(const FHotPatcherVersion& Base, const FHotPatcherVersion& New,FPatchVersionDiff& PakDiff,int32 flags) +{ + UClass* UMaterialClass = UMaterial::StaticClass(); + TArray MaterialInstanceClasses = GetDerivedClasses(UMaterialInstance::StaticClass(),true,true); + UFlibHotPatcherCoreHelper::AnalysisDependenciesAssets(Base,New,PakDiff,UMaterialClass,MaterialInstanceClasses,false); +} + +void UFlibHotPatcherCoreHelper::AnalysisDependenciesAssets(const FHotPatcherVersion& Base, const FHotPatcherVersion& New,FPatchVersionDiff& PakDiff,UClass* SearchClass,TArray DependenciesClass,bool bRecursive,int32 flags) +{ + TArray AnalysisAssets; + if(flags & 0x1) + { + const TArray& AddAssets = PakDiff.AssetDiffInfo.AddAssetDependInfo.GetAssetDetails(); + AnalysisAssets.Append(AddAssets); + + } + if(flags & 0x2) + { + const TArray& ModifyAssets = PakDiff.AssetDiffInfo.ModifyAssetDependInfo.GetAssetDetails(); + AnalysisAssets.Append(ModifyAssets); + } + TArray AssetRegistryDepTypes {EAssetRegistryDependencyTypeEx::Hard}; + + FName ClassName = SearchClass->GetFName();; + + for(const auto& OriginAsset:AnalysisAssets) + { + if(OriginAsset.AssetType.IsEqual(ClassName)) + { + for(auto& DepencenciesClassItem:DependenciesClass) + { + FName DependenciesName = DepencenciesClassItem->GetFName(); + TArray RefAssets = UFlibHotPatcherCoreHelper::GetReferenceRecursivelyByClassName(OriginAsset,TArray{DependenciesName.ToString()},AssetRegistryDepTypes,bRecursive); + for(const auto& Asset:RefAssets) + { + if(!(Asset.AssetType.IsEqual(DependenciesName))) + { + continue; + } + FString PackageName = UFlibAssetManageHelper::PackagePathToLongPackageName(Asset.PackagePath.ToString()); + bool bInBaseVersion = Base.AssetInfo.HasAsset(PackageName); + if(bInBaseVersion) + { + PakDiff.AssetDiffInfo.ModifyAssetDependInfo.AddAssetsDetail(Asset); + UE_LOG(LogHotPatcher,Log,TEXT("Add Parent: %s"),*Asset.PackagePath.ToString()); + } + } + } + } + } +} + +TArray UFlibHotPatcherCoreHelper::GetReferenceRecursivelyByClassName(const FAssetDetail& AssetDetail,const TArray& AssetTypeNames,const TArray& RefType,bool bRecursive) +{ + TArray Results; + + TArray AssetRegistryDepTypes {EAssetRegistryDependencyTypeEx::Hard}; + TArray SearchTypes; + for(auto TypeEx:AssetRegistryDepTypes) + { + SearchTypes.AddUnique(UFlibAssetManageHelper::ConvAssetRegistryDependencyToInternal(TypeEx)); + } + + TArray CurrentAssetsRef; + UFlibAssetManageHelper::GetAssetReferenceRecursively(AssetDetail, SearchTypes, AssetTypeNames, CurrentAssetsRef,bRecursive); + for(const auto& Asset:CurrentAssetsRef) + { + if(!AssetTypeNames.Contains(Asset.AssetType.ToString())) + { + continue; + } + Results.AddUnique(Asset); + } + + return Results; +} + +TArray UFlibHotPatcherCoreHelper::GetDerivedClasses(UClass* BaseClass,bool bRecursive, bool bContainSelf) +{ + TArray Classes; + if(bContainSelf) + { + Classes.AddUnique(BaseClass); + } + + TArray AllDerivedClass; + ::GetDerivedClasses(BaseClass,AllDerivedClass,bRecursive); + for(auto classIns:AllDerivedClass) + { + Classes.AddUnique(classIns); + } + return Classes; +} + +void UFlibHotPatcherCoreHelper::DeleteDirectory(const FString& Dir) +{ + if(!Dir.IsEmpty() && FPaths::DirectoryExists(Dir)) + { + UE_LOG(LogHotPatcher,Display,TEXT("delete dir %s"),*Dir); + IFileManager::Get().DeleteDirectory(*Dir,true,true); + } +} + +int32 UFlibHotPatcherCoreHelper::GetMemoryMappingAlignment(const FString& PlatformName) +{ +#if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 22 + int32 result = 0; + + ITargetPlatform* Platform = UFlibHotPatcherCoreHelper::GetPlatformByName(PlatformName); + if(Platform) + { + result = Platform->GetMemoryMappingAlignment(); + } + return result; +#else + if(PlatformName.Equals(TEXT("IOS"))) + { + return 16384; + } + return 0; +#endif +} + +FChunkAssetDescribe UFlibHotPatcherCoreHelper::DiffChunkWithPatchSetting( + const FExportPatchSettings& PatchSetting, + const FChunkInfo& CurrentVersionChunk, + const FChunkInfo& TotalChunk + //,TMap& ScanedCaches +) +{ + FHotPatcherVersion TotalChunkVersion = UFlibPatchParserHelper::ExportReleaseVersionInfoByChunk( + TEXT(""), + TEXT(""), + TEXT(""), + TotalChunk, + //ScanedCaches, + PatchSetting.IsIncludeHasRefAssetsOnly(), + TotalChunk.bAnalysisFilterDependencies, + PatchSetting.GetHashCalculator() + ); + + return UFlibHotPatcherCoreHelper::DiffChunkByBaseVersionWithPatchSetting(PatchSetting, CurrentVersionChunk ,TotalChunk, TotalChunkVersion/*,ScanedCaches*/); +} + +FChunkAssetDescribe UFlibHotPatcherCoreHelper::DiffChunkByBaseVersionWithPatchSetting( + const FExportPatchSettings& PatchSetting, + const FChunkInfo& CurrentVersionChunk, + const FChunkInfo& TotalChunk, + const FHotPatcherVersion& BaseVersion + //,TMap& ScanedCaches +) +{ + FChunkAssetDescribe result; + FHotPatcherVersion CurrentVersion = UFlibPatchParserHelper::ExportReleaseVersionInfoByChunk( + TEXT(""), + TEXT(""), + TEXT(""), + CurrentVersionChunk, + //ScanedCaches, + PatchSetting.IsIncludeHasRefAssetsOnly(), + CurrentVersionChunk.bAnalysisFilterDependencies, + PatchSetting.GetHashCalculator() + ); + FPatchVersionDiff ChunkDiffInfo = UFlibHotPatcherCoreHelper::DiffPatchVersionWithPatchSetting(PatchSetting, BaseVersion, CurrentVersion); + + result.Assets = UFlibAssetManageHelper::CombineAssetDependencies(ChunkDiffInfo.AssetDiffInfo.AddAssetDependInfo, ChunkDiffInfo.AssetDiffInfo.ModifyAssetDependInfo); + + TArray Platforms; + ChunkDiffInfo.PlatformExternDiffInfo.GetKeys(Platforms); + for(auto Platform:Platforms) + { + FPlatformExternFiles PlatformFiles; + PlatformFiles.Platform = Platform; + PlatformFiles.ExternFiles = ChunkDiffInfo.PlatformExternDiffInfo.Find(Platform)->AddExternalFiles; + PlatformFiles.ExternFiles.Append(ChunkDiffInfo.PlatformExternDiffInfo.Find(Platform)->ModifyExternalFiles); + result.AllPlatformExFiles.Add(Platform,PlatformFiles); + } + + result.InternalFiles.bIncludeAssetRegistry = CurrentVersionChunk.InternalFiles.bIncludeAssetRegistry != TotalChunk.InternalFiles.bIncludeAssetRegistry; + result.InternalFiles.bIncludeGlobalShaderCache = CurrentVersionChunk.InternalFiles.bIncludeGlobalShaderCache != TotalChunk.InternalFiles.bIncludeGlobalShaderCache; + result.InternalFiles.bIncludeShaderBytecode = CurrentVersionChunk.InternalFiles.bIncludeShaderBytecode != TotalChunk.InternalFiles.bIncludeShaderBytecode; + result.InternalFiles.bIncludeEngineIni = CurrentVersionChunk.InternalFiles.bIncludeEngineIni != TotalChunk.InternalFiles.bIncludeEngineIni; + result.InternalFiles.bIncludePluginIni = CurrentVersionChunk.InternalFiles.bIncludePluginIni != TotalChunk.InternalFiles.bIncludePluginIni; + result.InternalFiles.bIncludeProjectIni = CurrentVersionChunk.InternalFiles.bIncludeProjectIni != TotalChunk.InternalFiles.bIncludeProjectIni; + + return result; +} + + +bool UFlibHotPatcherCoreHelper::SerializeAssetRegistryByDetails(IAssetRegistry* AssetRegistry, + const FString& PlatformName, const TArray& AssetDetails, const FString& SavePath) +{ + + ITargetPlatform* TargetPlatform = UFlibHotPatcherCoreHelper::GetPlatformByName(PlatformName); + FAssetRegistrySerializationOptions SaveOptions; + AssetRegistry->InitializeSerializationOptions(SaveOptions, TargetPlatform->IniPlatformName()); + SaveOptions.bSerializeAssetRegistry = true; + + return UFlibHotPatcherCoreHelper::SerializeAssetRegistryByDetails(AssetRegistry,PlatformName,AssetDetails,SavePath, SaveOptions); +} + +bool UFlibHotPatcherCoreHelper::SerializeAssetRegistryByDetails(IAssetRegistry* AssetRegistry, + const FString& PlatformName, const TArray& AssetDetails, const FString& SavePath, FAssetRegistrySerializationOptions SaveOptions) +{ + TArray PackagePaths; + + for(const auto& Detail:AssetDetails) + { + PackagePaths.AddUnique(Detail.PackagePath.ToString()); + } + + return UFlibHotPatcherCoreHelper::SerializeAssetRegistry(AssetRegistry,PlatformName,PackagePaths,SavePath, SaveOptions); +} + +bool UFlibHotPatcherCoreHelper::SerializeAssetRegistry(IAssetRegistry* AssetRegistry, + const FString& PlatformName, const TArray& PackagePaths, const FString& SavePath, FAssetRegistrySerializationOptions SaveOptions) +{ + + FAssetRegistryState State; + // FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + // IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); + + AssetRegistry->Tick(-1.0f); + AssetRegistry->InitializeTemporaryAssetRegistryState(State, SaveOptions, true); + for(const auto& AssetPackagePath:PackagePaths) + { + if (State.GetAssetByObjectPath(FName(*AssetPackagePath))) + { + UE_LOG(LogHotPatcherCoreHelper, Warning, TEXT("%s already add to AssetRegistryState!"), *AssetPackagePath); + continue; + } + FAssetData* AssetData = new FAssetData(); + if (UFlibAssetManageHelper::GetSingleAssetsData(AssetPackagePath, *AssetData)) + { + if (AssetPackagePath != AssetData->ObjectPath.ToString()) + { + UE_LOG(LogHotPatcherCoreHelper, Warning, TEXT("%s is a redirector of %s, skip!"), *AssetPackagePath, *AssetData->ObjectPath.ToString()); + delete AssetData; + continue; + } + State.AddAssetData(AssetData); + continue; + } + delete AssetData; + } + // Create runtime registry data + FArrayWriter SerializedAssetRegistry; + SerializedAssetRegistry.SetFilterEditorOnly(true); + +#if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 26 + bool bStateSave = State.Save(SerializedAssetRegistry, SaveOptions); +#else + bool bStateSave = State.Serialize(SerializedAssetRegistry, SaveOptions); +#endif + bool result = false; + // Save the generated registry + if(bStateSave && FFileHelper::SaveArrayToFile(SerializedAssetRegistry, *SavePath)) + { + result = true; + UE_LOG(LogHotPatcher,Log,TEXT("Serialize %s AssetRegistry"),*SavePath); + } + return result; +} + + +FHotPatcherVersion UFlibHotPatcherCoreHelper::MakeNewRelease(const FHotPatcherVersion& InBaseVersion, const FHotPatcherVersion& InCurrentVersion, FExportPatchSettings* InPatchSettings) +{ + FHotPatcherVersion BaseVersion = InBaseVersion; + + FPatchVersionDiff DiffInfo = UFlibHotPatcherCoreHelper::DiffPatchVersionWithPatchSetting(*InPatchSettings,BaseVersion, InCurrentVersion); + return UFlibHotPatcherCoreHelper::MakeNewReleaseByDiff(InBaseVersion,DiffInfo, InPatchSettings); +} + +FHotPatcherVersion UFlibHotPatcherCoreHelper::MakeNewReleaseByDiff(const FHotPatcherVersion& InBaseVersion, + const FPatchVersionDiff& InDiff, FExportPatchSettings* InPatchSettings) +{ + FHotPatcherVersion BaseVersion = InBaseVersion; + FHotPatcherVersion NewRelease; + + NewRelease.BaseVersionId = InBaseVersion.VersionId; + NewRelease.Date = FDateTime::UtcNow().ToString(); + NewRelease.VersionId = InPatchSettings ? InPatchSettings->VersionId : TEXT(""); + + FAssetDependenciesInfo& BaseAssetInfoRef = BaseVersion.AssetInfo; + // TMap& BaseExternalFilesRef = BaseVersion.ExternalFiles; + TMap& BasePlatformAssetsRef = BaseVersion.PlatformAssets; + + // Modify Asset + auto DeleteOldAssetLambda = [&BaseAssetInfoRef](const FAssetDependenciesInfo& InAssetDependenciesInfo) + { + for (const auto& AssetsModulePair : InAssetDependenciesInfo.AssetsDependenciesMap) + { + FAssetDependenciesDetail* NewReleaseModuleAssets = BaseAssetInfoRef.AssetsDependenciesMap.Find(AssetsModulePair.Key); + + for (const auto& NeedDeleteAsset : AssetsModulePair.Value.AssetDependencyDetails) + { + if (NewReleaseModuleAssets && NewReleaseModuleAssets->AssetDependencyDetails.Contains(NeedDeleteAsset.Key)) + { + NewReleaseModuleAssets->AssetDependencyDetails.Remove(NeedDeleteAsset.Key); + } + } + } + }; + + DeleteOldAssetLambda(InDiff.AssetDiffInfo.ModifyAssetDependInfo); + if(InPatchSettings && !InPatchSettings->IsSaveDeletedAssetsToNewReleaseJson()) + { + DeleteOldAssetLambda(InDiff.AssetDiffInfo.DeleteAssetDependInfo); + } + + // Add Asset + BaseAssetInfoRef = UFlibAssetManageHelper::CombineAssetDependencies(BaseAssetInfoRef, InDiff.AssetDiffInfo.AddAssetDependInfo); + // modify Asset + BaseAssetInfoRef = UFlibAssetManageHelper::CombineAssetDependencies(BaseAssetInfoRef, InDiff.AssetDiffInfo.ModifyAssetDependInfo); + NewRelease.AssetInfo = BaseAssetInfoRef; + + // // external files + // auto RemoveOldExternalFilesLambda = [&BaseExternalFilesRef](const TArray& InFiles) + // { + // for (const auto& File : InFiles) + // { + // if (BaseExternalFilesRef.Contains(File.FilePath.FilePath)) + // { + // BaseExternalFilesRef.Remove(File.FilePath.FilePath); + // } + // } + // }; + + TArray DiffPlatforms; + InDiff.PlatformExternDiffInfo.GetKeys(DiffPlatforms); + + for(auto Platform:DiffPlatforms) + { + FPlatformExternAssets AddPlatformFiles; + AddPlatformFiles.TargetPlatform = Platform; + AddPlatformFiles.AddExternFileToPak = InDiff.PlatformExternDiffInfo[Platform].AddExternalFiles; + AddPlatformFiles.AddExternFileToPak.Append(InDiff.PlatformExternDiffInfo[Platform].ModifyExternalFiles); + if(BasePlatformAssetsRef.Contains(Platform)) + { + for(const auto& File:AddPlatformFiles.AddExternFileToPak) + { + if(BasePlatformAssetsRef[Platform].AddExternFileToPak.Contains(File)) + { + BasePlatformAssetsRef[Platform].AddExternFileToPak.Remove(File); + } + BasePlatformAssetsRef[Platform].AddExternFileToPak.Add(File); + } + }else + { + BasePlatformAssetsRef.Add(Platform,AddPlatformFiles); + } + } + // RemoveOldExternalFilesLambda(DiffInfo.ExternDiffInfo.ModifyExternalFiles); + // DeleteOldExternalFilesLambda(DiffInfo.DeleteExternalFiles); + + NewRelease.PlatformAssets = BasePlatformAssetsRef; + return NewRelease; +} + + +FString UFlibHotPatcherCoreHelper::GetPakCommandExtersion(const FString& InCommand) +{ + auto GetPakCommandFileExtensionLambda = [](const FString& Command)->FString + { + FString result; + int32 DotPos = Command.Find(TEXT("."), ESearchCase::CaseSensitive, ESearchDir::FromEnd); + if (DotPos != INDEX_NONE) + { + result = Command.Mid(DotPos + 1); + int32 FirstDoubleQuotesPos = -1; + if(result.FindChar('"',FirstDoubleQuotesPos)) + { + result.RemoveAt(FirstDoubleQuotesPos,result.Len()-FirstDoubleQuotesPos); + } + } + return result; + }; + return GetPakCommandFileExtensionLambda(InCommand); +} + +TArray UFlibHotPatcherCoreHelper::GetExtensionsToNotUsePluginCompressionByGConfig() +{ + TArray IgnoreCompressFormats; + GConfig->GetArray(TEXT("Pak"),TEXT("ExtensionsToNotUsePluginCompression"),IgnoreCompressFormats,GEngineIni); + return IgnoreCompressFormats; +} + +void UFlibHotPatcherCoreHelper::AppendPakCommandOptions(TArray& OriginCommands, + const TArray& Options, bool bAppendAllMatch, const TArray& AppendFileExtersions, + const TArray& IgnoreFormats, const TArray& InIgnoreOptions) +{ + ParallelFor(OriginCommands.Num(),[&](int32 index) + { + FString& Command = OriginCommands[index]; + FString PakOptionsStr; + for (const auto& Param : Options) + { + FString FileExtension = UFlibHotPatcherCoreHelper::GetPakCommandExtersion(Command); + if(IgnoreFormats.Contains(FileExtension) && InIgnoreOptions.Contains(Param)) + { + continue; + } + + FString AppendOptionStr = TEXT(""); + if(bAppendAllMatch || AppendFileExtersions.Contains(FileExtension)) + { + AppendOptionStr += TEXT(" ") + Param; + } + + PakOptionsStr += AppendOptionStr; + } + Command = FString::Printf(TEXT("%s%s"),*Command,*PakOptionsStr); + }); +} + +FProjectPackageAssetCollection UFlibHotPatcherCoreHelper::ImportProjectSettingsPackages() +{ + FProjectPackageAssetCollection result; + TArray& DirectoryPaths = result.DirectoryPaths; + TArray& SoftObjectPaths = result.NeedCookPackages; + + auto AddSoftObjectPath = [&](const FString& LongPackageName) + { + bool bSuccessed = false; + // PRAGMA_DISABLE_DEPRECATION_WARNINGS + if (UFlibHotPatcherCoreHelper::IsCanCookPackage(LongPackageName)) + { + FString LongPackagePath = LongPackageName.Contains(TEXT(".")) ? LongPackageName : UFlibAssetManageHelper::LongPackageNameToPackagePath(LongPackageName); + FSoftObjectPath CurrentObject(LongPackagePath); + if(FPackageName::DoesPackageExist(LongPackagePath) && CurrentObject.IsValid()) + { + // UE_LOG(LogHotPatcherCoreHelper,Display,TEXT("Import Project Setting Package: %s"),*LongPackagePath); + SoftObjectPaths.AddUnique(CurrentObject); + bSuccessed = true; + } + } + // PRAGMA_ENABLE_DEPRECATION_WARNINGS + if(!bSuccessed) + { + // UE_LOG(LogHotPatcherCoreHelper,Warning,TEXT("Import Project Setting Package: %s is inavlid!"),*LongPackagePath); + } + }; + + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + IAssetRegistry* AssetRegistry = &AssetRegistryModule.Get(); + + const UProjectPackagingSettings* const PackagingSettings = GetDefault(); + + { + // allow the game to fill out the asset registry, as well as get a list of objects to always cook + TArray FilesInPathStrings; + PRAGMA_DISABLE_DEPRECATION_WARNINGS; + FGameDelegates::Get().GetCookModificationDelegate().ExecuteIfBound(FilesInPathStrings); + PRAGMA_ENABLE_DEPRECATION_WARNINGS; + for(const auto& BuildFilename:FilesInPathStrings) + { + FString OutPackageName; + if (FPackageName::TryConvertFilenameToLongPackageName(FPaths::ConvertRelativePathToFull(BuildFilename), OutPackageName)) + { + AddSoftObjectPath(OutPackageName); + } + } + } + + // in Asset Manager / PrimaryAssetLabel + { + TArray PackageToCook; + TArray PackageToNeverCook; +#if ENGINE_MAJOR_VERSION > 4 + ITargetPlatformManagerModule* TPM = GetTargetPlatformManager(); + const TArray& Platforms = TPM->GetActiveTargetPlatforms(); + UAssetManager::Get().ModifyCook(Platforms, PackageToCook, PackageToNeverCook); +#else + UAssetManager::Get().ModifyCook(PackageToCook, PackageToNeverCook); +#endif + + + for(const auto& Package:PackageToCook) + { + AddSoftObjectPath(Package.ToString()); + } + for(const auto& NeverCookPackage:PackageToNeverCook) + { + result.NeverCookPackages.Add(NeverCookPackage.ToString()); + } + } + + // DirectoriesToAlwaysCook + DirectoryPaths.Append(PackagingSettings->DirectoriesToAlwaysCook); + + // AlwaysCookMaps + { + TArray MapList; + // Add the default map section + GEditor->LoadMapListFromIni(TEXT("AlwaysCookMaps"), MapList); + + for (int32 MapIdx = 0; MapIdx < MapList.Num(); MapIdx++) + { + FName PackageName = FName(*FPackageName::FilenameToLongPackageName(FPaths::ConvertRelativePathToFull(MapList[MapIdx]))); + AddSoftObjectPath(PackageName.ToString()); + } + } + + // MapsToCook + for (const FFilePath& MapToCook : PackagingSettings->MapsToCook) + { + FString File = MapToCook.FilePath; + FName PackageName = FName(*FPackageName::FilenameToLongPackageName(FPaths::ConvertRelativePathToFull(File))); + AddSoftObjectPath(PackageName.ToString()); + } + + // Loading default map ini section AllMaps + { + TArray AllMapsSection; + GEditor->LoadMapListFromIni(TEXT("AllMaps"), AllMapsSection); + for (const FString& MapName : AllMapsSection) + { + AddSoftObjectPath(MapName); + } + } + + // all uasset and umap + if(!SoftObjectPaths.Num() && !DirectoryPaths.Num()) + { + TArray Tokens; + Tokens.Empty(2); + Tokens.Add(FString("*") + FPackageName::GetAssetPackageExtension()); + Tokens.Add(FString("*") + FPackageName::GetMapPackageExtension()); + + uint8 PackageFilter = NORMALIZE_DefaultFlags | NORMALIZE_ExcludeEnginePackages | NORMALIZE_ExcludeLocalizedPackages; + bool bMapsOnly = false; + if (bMapsOnly) + { + PackageFilter |= NORMALIZE_ExcludeContentPackages; + } + bool bNoDev = false; + if (bNoDev) + { + PackageFilter |= NORMALIZE_ExcludeDeveloperPackages; + } + + // assume the first token is the map wildcard/pathname + TArray Unused; + for (int32 TokenIndex = 0; TokenIndex < Tokens.Num(); TokenIndex++) + { + TArray TokenFiles; + if (!NormalizePackageNames(Unused, TokenFiles, Tokens[TokenIndex], PackageFilter)) + { + UE_LOG(LogHotPatcherCoreHelper, Display, TEXT("No packages found for parameter %i: '%s'"), TokenIndex, *Tokens[TokenIndex]); + continue; + } + + for (int32 TokenFileIndex = 0; TokenFileIndex < TokenFiles.Num(); ++TokenFileIndex) + { + FName PackageName = FName(*FPackageName::FilenameToLongPackageName(FPaths::ConvertRelativePathToFull(TokenFiles[TokenFileIndex]))); + AddSoftObjectPath(PackageName.ToString()); + } + } + } + + // =============================================== + + { + TSet StartupPackages; + TSet StartupSoftObjectPackages; + + for (TObjectIterator It; It; ++It) + { + if ((*It) != GetTransientPackage()) + { + if(!It->GetFName().ToString().StartsWith(TEXT("/Script"))) + { + StartupPackages.Add(It->GetFName()); + } + } + } + + // Get the list of soft references, for both empty package and all startup packages + GRedirectCollector.ProcessSoftObjectPathPackageList(NAME_None, false, StartupSoftObjectPackages); + + for (const FName& StartupPackage : StartupPackages) + { + GRedirectCollector.ProcessSoftObjectPathPackageList(StartupPackage, false, StartupSoftObjectPackages); + } + + // Add string asset packages after collecting files, to avoid accidentally activating the behavior to cook all maps if none are specified + for (FName SoftObjectPackage : StartupSoftObjectPackages) + { + AddSoftObjectPath(SoftObjectPackage.ToString()); + } + } + // Find all the localized packages and map them back to their source package + { + TArray AllCulturesToCook; + for (const FString& CultureName : PackagingSettings->CulturesToStage) + { + const TArray PrioritizedCultureNames = FInternationalization::Get().GetPrioritizedCultureNames(CultureName); + for (const FString& PrioritizedCultureName : PrioritizedCultureNames) + { + AllCulturesToCook.AddUnique(PrioritizedCultureName); + } + } + AllCulturesToCook.Sort(); + + TArray RootPaths; + FPackageName::QueryRootContentPaths(RootPaths); + + FARFilter Filter; + Filter.bRecursivePaths = true; + Filter.bIncludeOnlyOnDiskAssets = false; + Filter.PackagePaths.Reserve(AllCulturesToCook.Num() * RootPaths.Num()); + for (const FString& RootPath : RootPaths) + { + for (const FString& CultureName : AllCulturesToCook) + { + FString LocalizedPackagePath = RootPath / TEXT("L10N") / CultureName; + Filter.PackagePaths.Add(*LocalizedPackagePath); + } + } + + TArray AssetDataForCultures; + AssetRegistry->GetAssets(Filter, AssetDataForCultures); + + for (const FAssetData& AssetData : AssetDataForCultures) + { + const FName LocalizedPackageName = AssetData.PackageName; + const FName SourcePackageName = *FPackageName::GetSourcePackagePath(LocalizedPackageName.ToString()); + AddSoftObjectPath(LocalizedPackageName.ToString()); + } + } + const UGameMapsSettings* const GameMapsSettings = GetDefault(); + { + if(GameMapsSettings->GameInstanceClass.IsAsset()) + { + AddSoftObjectPath(GameMapsSettings->GameInstanceClass.GetAssetPathString()); + } + + FSoftObjectPath GameDefaultMap{ + (GameMapsSettings->GetGameDefaultMap()) + }; + if(GameDefaultMap.IsAsset()) + { + AddSoftObjectPath(GameDefaultMap.GetAssetPathString()); + } + + FSoftObjectPath DefaultGameMode{ + (GameMapsSettings->GetGlobalDefaultGameMode()) + }; + if(DefaultGameMode.IsAsset()) + { + AddSoftObjectPath(DefaultGameMode.GetAssetPathString()); + } + + if(GameMapsSettings->TransitionMap.IsAsset()) + { + AddSoftObjectPath(GameMapsSettings->TransitionMap.GetAssetPathString()); + } + } + auto CreateDirectory = [](const FString& Path) + { + FDirectoryPath DirectoryPath; + DirectoryPath.Path = Path; + return DirectoryPath; + }; + + + + // DirectoryPaths.AddUnique(CreateDirectory("/Game/UI")); + // DirectoryPaths.AddUnique(CreateDirectory("/Game/Widget")); + // DirectoryPaths.AddUnique(CreateDirectory("/Game/Widgets")); + // DirectoryPaths.AddUnique(CreateDirectory("/Engine/MobileResources")); + { + TArray UIContentPaths; + TSet ContentDirectoryAssets; + if (GConfig->GetArray(TEXT("UI"), TEXT("ContentDirectories"), UIContentPaths, GEditorIni) > 0) + { + for (int32 DirIdx = 0; DirIdx < UIContentPaths.Num(); DirIdx++) + { + DirectoryPaths.Add(CreateDirectory(UIContentPaths[DirIdx])); + } + } + } + + { + FConfigFile InputIni; + FString InterfaceFile; + FConfigCacheIni::LoadLocalIniFile(InputIni, TEXT("Input"), true); + if (InputIni.GetString(TEXT("/Script/Engine.InputSettings"), TEXT("DefaultTouchInterface"), InterfaceFile)) + { + if (InterfaceFile != TEXT("None") && InterfaceFile != TEXT("")) + { + AddSoftObjectPath(InterfaceFile); + } + } + } + + { + TArray AllCulturesToCook = PackagingSettings->CulturesToStage;; + + TArray RootPaths; + FPackageName::QueryRootContentPaths(RootPaths); + + FARFilter Filter; + Filter.bRecursivePaths = true; + Filter.bIncludeOnlyOnDiskAssets = false; + Filter.PackagePaths.Reserve(AllCulturesToCook.Num() * RootPaths.Num()); + for (const FString& RootPath : RootPaths) + { + for (const FString& CultureName : AllCulturesToCook) + { + FString LocalizedPackagePath = RootPath / TEXT("L10N") / CultureName; + Filter.PackagePaths.Add(*LocalizedPackagePath); + } + } + + + TArray AssetDataForCultures; + AssetRegistry->GetAssets(Filter, AssetDataForCultures); + + for (const FAssetData& AssetData : AssetDataForCultures) + { + // const FName LocalizedPackageName = AssetData.PackageName; + // const FName SourcePackageName = *FPackageName::GetSourcePackagePath(LocalizedPackageName.ToString()); + + AddSoftObjectPath(AssetData.PackageName.ToString()); + } + } + + // GetUnsolicitedPackages + { + for (TObjectIterator It; It; ++It) + { + UPackage* Package = *It; + + if (Package->GetOuter() == nullptr) + { + AddSoftObjectPath(Package->GetName()); + } + } + } + + // never cook packages + result.NeverCookPaths.Append(PackagingSettings->DirectoriesToNeverCook); + + return result; +} + +void UFlibHotPatcherCoreHelper::WaitForAsyncFileWrites() +{ + SCOPED_NAMED_EVENT_TEXT("WaitForAsyncFileWrites",FColor::Red); + // UE_LOG(LogHotPatcher, Display, TEXT("Wait For Async File Writes...")); + TSharedPtr WaitThreadWorker = MakeShareable(new FThreadWorker(TEXT("WaitCookComplete"),[]() + { + UPackage::WaitForAsyncFileWrites(); + })); + WaitThreadWorker->Execute(); + WaitThreadWorker->Join(); +} + +void UFlibHotPatcherCoreHelper::WaitDDCComplete() +{ + SCOPED_NAMED_EVENT_TEXT("WaitDDCComplete",FColor::Red); + GetDerivedDataCacheRef().WaitForQuiescence(true); +} + +bool UFlibHotPatcherCoreHelper::IsCanCookPackage(const FString& LongPackageName) +{ + bool bResult = false; + PRAGMA_DISABLE_DEPRECATION_WARNINGS + if (!LongPackageName.IsEmpty() && !FPackageName::IsScriptPackage(LongPackageName) && !FPackageName::IsMemoryPackage(LongPackageName)) + { + bResult = UAssetManager::Get().VerifyCanCookPackage(FName(*LongPackageName),false); + } + PRAGMA_ENABLE_DEPRECATION_WARNINGS + return bResult; +} + +void UFlibHotPatcherCoreHelper::ImportProjectSettingsToScannerConfig(FAssetScanConfig& AssetScanConfig) +{ + FProjectPackageAssetCollection AssetCollection = UFlibHotPatcherCoreHelper::ImportProjectSettingsPackages(); + AssetScanConfig.AssetIncludeFilters.Append(AssetCollection.DirectoryPaths); + + for(const auto& Asset:AssetCollection.NeedCookPackages) + { + FPatcherSpecifyAsset CurrentAsset; + CurrentAsset.Asset = Asset; + CurrentAsset.bAnalysisAssetDependencies = true; + CurrentAsset.AssetRegistryDependencyTypes = {EAssetRegistryDependencyTypeEx::Packages}; + AssetScanConfig.IncludeSpecifyAssets.Add(CurrentAsset); + } + // NEVER COOK + AssetScanConfig.AssetIgnoreFilters.Append(AssetCollection.NeverCookPaths); + AssetScanConfig.ForceSkipContentRules.Append(AssetCollection.NeverCookPaths); + AssetScanConfig.ForceSkipAssets.Append(AssetCollection.NeverCookPackages); +} + +void UFlibHotPatcherCoreHelper::ImportProjectNotAssetDir(TArray& PlatformExternAssets, ETargetPlatform AddToTargetPlatform) +{ + const auto ProjectNotAssetDirs = UFlibHotPatcherCoreHelper::GetProjectNotAssetDirConfig(); + FPlatformExternAssets* AddToPlatformExternalAssetsPtr = nullptr; + + for(auto& AddExternAssetsToPlatform:PlatformExternAssets) + { + if(AddExternAssetsToPlatform.TargetPlatform == AddToTargetPlatform) + { + AddToPlatformExternalAssetsPtr = &AddExternAssetsToPlatform; + } + } + if(!AddToPlatformExternalAssetsPtr) + { + FPlatformExternAssets AllPlatformExternalAssets; + AllPlatformExternalAssets.TargetPlatform = AddToTargetPlatform; + + int32 index = PlatformExternAssets.Add(AllPlatformExternalAssets); + AddToPlatformExternalAssetsPtr = &PlatformExternAssets[index]; + } + AddToPlatformExternalAssetsPtr->AddExternDirectoryToPak.Append(ProjectNotAssetDirs); +} + +TArray UFlibHotPatcherCoreHelper::GetProjectNotAssetDirConfig() +{ + TArray result; + const UProjectPackagingSettings* const PackagingSettings = GetDefault(); + + FString BasePath = FString::Printf(TEXT("../../../%s/Content/"),FApp::GetProjectName()); + auto FixPath = [](const FString& BasePath,const FString& Path)->FString + { + FString result; + FString finalSubPath = Path; + TArray PathsItem = UKismetStringLibrary::ParseIntoArray(BasePath,TEXT("/")); + while(PathsItem.Num() && finalSubPath.StartsWith(TEXT("../"))) + { + PathsItem.RemoveAt(PathsItem.Num()-1); + finalSubPath.RemoveFromStart(TEXT("../")); + } + PathsItem.Add(finalSubPath); + for(auto& DirItem:PathsItem) + { + if(!DirItem.IsEmpty()) + { + result = FPaths::Combine(result,DirItem); + } + } + return result; + }; + + if(PackagingSettings) + { + for(auto ContentSubDir:PackagingSettings->DirectoriesToAlwaysStageAsUFS) + { + FExternDirectoryInfo DirInfo; + + FString MountPoint = ContentSubDir.Path; + if(MountPoint.StartsWith(TEXT("../"))) + { + MountPoint = FixPath(BasePath,MountPoint); + DirInfo.DirectoryPath.Path = FixPath(TEXT("[PROJECTDIR]/Content"),ContentSubDir.Path); + } + else + { + MountPoint = FPaths::Combine(BasePath,MountPoint); + DirInfo.DirectoryPath.Path = FString::Printf(TEXT("[PROJECT_CONTENT_DIR]/%s"),*ContentSubDir.Path); + } + DirInfo.MountPoint = MountPoint; + FPaths::NormalizeDirectoryName(DirInfo.MountPoint); + result.Emplace(DirInfo); + } + } + return result; +} + +void UFlibHotPatcherCoreHelper::CacheForCookedPlatformData( + const TArray& ObjectPaths, + TArray TargetPlatforms, + TSet& ProcessedObjs, + TSet& PendingCachePlatformDataObjects, + bool bStorageConcurrent, + bool bWaitComplete, + TFunction OnPreCacheObjectWithOuter + ) +{ + SCOPED_NAMED_EVENT_TEXT("CacheForCookedPlatformData",FColor::Red); + TArray AllPackages = UFlibAssetManageHelper::LoadPackagesForCooking(ObjectPaths,bStorageConcurrent); + { + SCOPED_NAMED_EVENT_TEXT("BeginCacheForCookedPlatformData for Assets",FColor::Red); + UFlibHotPatcherCoreHelper::CacheForCookedPlatformData(AllPackages,TargetPlatforms,ProcessedObjs,PendingCachePlatformDataObjects, bStorageConcurrent, bWaitComplete); + } + + if(bWaitComplete) + { + SCOPED_NAMED_EVENT_TEXT("WaitCacheForCookedPlatformDataComplete",FColor::Red); + // Wait for all shaders to finish compiling + UFlibShaderCodeLibraryHelper::WaitShaderCompilingComplete(); + // UFlibHotPatcherCoreHelper::WaitForAsyncFileWrites(); + } +} + + +#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION > 25 +UE_TRACE_EVENT_BEGIN(CUSTOM_LOADTIMER_LOG, CachePackagePlatformData, NoSync) + UE_TRACE_EVENT_FIELD(Trace::WideString, PackageName) +UE_TRACE_EVENT_END() + +#endif +void UFlibHotPatcherCoreHelper::CacheForCookedPlatformData( + const TArray& Packages, + TArray TargetPlatforms, + TSet& ProcessedObjs, + TSet& PendingCachePlatformDataObjects, + bool bStorageConcurrent, + bool bWaitComplete, + TFunction OnPreCacheObjectWithOuter + ) +{ + SCOPED_NAMED_EVENT_TEXT("CacheForCookedPlatformData",FColor::Red); + + TMap WorldsToPostSaveRoot; + WorldsToPostSaveRoot.Reserve(1024); + + for(auto Package:Packages) + { + FString LongPackageName = UFlibAssetManageHelper::LongPackageNameToPackagePath(Package->GetPathName()); + // FExecTimeRecoder PreGeneratePlatformDataTimer(FString::Printf(TEXT("PreGeneratePlatformData %s"),*LongPackageName)); + FString FakePackageName = FString(TEXT("Package ")) + LongPackageName; + +#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION > 25 + SCOPED_CUSTOM_LOADTIMER(CachePackagePlatformData) + ADD_CUSTOM_LOADTIMER_META(CachePackagePlatformData, PackageName, *FakePackageName); +#else + FScopedNamedEvent CachePackagePlatformDataEvent(FColor::Red,*FString::Printf(TEXT("%s"),*LongPackageName)); +#endif + + if(!Package) + { + UE_LOG(LogHotPatcher,Warning,TEXT("BeginPackageObjectsCacheForCookedPlatformData Package is null!")); + continue; + } + + { + SCOPED_NAMED_EVENT_TEXT("ExportMap BeginCacheForCookedPlatformData",FColor::Red); + + uint32 SaveFlags = UFlibHotPatcherCoreHelper::GetCookSaveFlag(Package,true,bStorageConcurrent,false); + TArray ObjectsInPackage; + GetObjectsWithOuter(Package,ObjectsInPackage,true); + for(const auto& ExportObj:ObjectsInPackage) + { + if(OnPreCacheObjectWithOuter) + { + OnPreCacheObjectWithOuter(Package,ExportObj); + } +#if ENGINE_MINOR_VERSION < 26 + FScopedNamedEvent CacheExportEvent(FColor::Red,*FString::Printf(TEXT("%s"),*ExportObj->GetName())); +#endif + if (ExportObj->HasAnyFlags(RF_Transient)) + { + // UE_LOG(LogHotPatcherCoreHelper, Display, TEXT("%s is PreCached."),*ExportObj->GetFullName()); + continue; + } + if(ProcessedObjs.Contains(ExportObj)) + { + continue;; + } + + bool bInitializedPhysicsSceneForSave = false; + bool bForceInitializedWorld = false; + UWorld* World = Cast(ExportObj); + if (World && bStorageConcurrent) + { + // We need a physics scene at save time in case code does traces during onsave events. + bInitializedPhysicsSceneForSave = GEditor->InitializePhysicsSceneForSaveIfNecessary(World, bForceInitializedWorld); + + GIsCookerLoadingPackage = true; + { + GEditor->OnPreSaveWorld(SaveFlags, World); + } + { + bool bCleanupIsRequired = World->PreSaveRoot(TEXT("")); + WorldsToPostSaveRoot.Add(World, bCleanupIsRequired); + } + GIsCookerLoadingPackage = false; + } + + if(ExportObj->GetClass()->GetName().Equals(TEXT("LandscapeComponent")) && bStorageConcurrent) + { + UE_LOG(LogHotPatcherCoreHelper,Display,TEXT("Object %s is a LandscapeComponent"),*ExportObj->GetFullName()); + TArray* MobileWeightmapTextures = nullptr; + for(TFieldIterator PropertyIter(ExportObj->GetClass());PropertyIter;++PropertyIter) + { + FProperty* PropertyIns = *PropertyIter; + if(PropertyIns->GetName().Equals(TEXT("MobileWeightmapTextures"))) + { + MobileWeightmapTextures = PropertyIns->ContainerPtrToValuePtr>(ExportObj); + break; + } + } + if(MobileWeightmapTextures) + { + UE_LOG(LogHotPatcherCoreHelper,Display,TEXT("Add %s MobileWeightmapTextures to ObjectsInPackage"),*ExportObj->GetFullName()); + for(UObject* MobileWeightmapTexture:*MobileWeightmapTextures) + { + ObjectsInPackage.AddUnique(MobileWeightmapTexture); + } + } + } + + for(const auto& Platform:TargetPlatforms) + { + if (bStorageConcurrent) + { + SCOPED_NAMED_EVENT_TEXT("Export PreSave",FColor::Red); + GIsCookerLoadingPackage = true; + { + ExportObj->PreSave(Platform); + } + GIsCookerLoadingPackage = false; + } + + // bool bIsTexture = ExportObj->IsA(UTexture::StaticClass()); + // if (!bIsTexture || bStorageConcurrent) + { + SCOPED_NAMED_EVENT_TEXT("BeginCacheForCookedPlatformData",FColor::Red); + + UPackage* Outermost = ExportObj->GetOutermost(); + bool bHasFilterEditorOnly = Outermost->HasAnyPackageFlags(PKG_FilterEditorOnly); + bool bIsTransient = (Outermost == GetTransientPackage()); + bool bIsTexture = ExportObj->IsA(UTexture::StaticClass()); + + ExportObj->BeginCacheForCookedPlatformData(Platform); + if(!(bIsTexture && bHasFilterEditorOnly) && !ExportObj->IsCachedCookedPlatformDataLoaded(Platform)) + { + PendingCachePlatformDataObjects.Add(ExportObj); + } + if(ExportObj->IsCachedCookedPlatformDataLoaded(Platform)) + { + ProcessedObjs.Add(ExportObj); + } + } + } + + if (World && bInitializedPhysicsSceneForSave) + { + SCOPED_NAMED_EVENT_TEXT("CleanupPhysicsSceneThatWasInitializedForSave",FColor::Red); + GEditor->CleanupPhysicsSceneThatWasInitializedForSave(World, bForceInitializedWorld); + } + } + } + if (bStorageConcurrent) + { + // Precache the metadata so we don't risk rehashing the map in the parallelfor below + Package->GetMetaData(); + } + } + + + if (bStorageConcurrent) + { + // UE_LOG(LogHotPatcherCoreHelper, Display, TEXT("Calling PostSaveRoot on worlds...")); + for (auto WorldIt = WorldsToPostSaveRoot.CreateConstIterator(); WorldIt; ++WorldIt) + { +#if ENGINE_MINOR_VERSION < 26 + FScopedNamedEvent CacheExportEvent(FColor::Red,*FString::Printf(TEXT("World PostSaveRoot"))); +#endif + UWorld* World = WorldIt.Key(); + check(World); + World->PostSaveRoot(WorldIt.Value()); + } + } + + // When saving concurrently, flush async loading since that is normally done internally in SavePackage + if (bStorageConcurrent) + { +#if ENGINE_MINOR_VERSION < 26 + FScopedNamedEvent CacheExportEvent(FColor::Red,*FString::Printf(TEXT("FlushAsyncLoading and ProcessThreadUtilIdle"))); +#endif + FlushAsyncLoading(); + FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread); + } + + if(bWaitComplete) + { + UFlibHotPatcherCoreHelper::WaitObjectsCachePlatformDataComplete(ProcessedObjs,PendingCachePlatformDataObjects,TargetPlatforms); + } + +} + +void UFlibHotPatcherCoreHelper::WaitObjectsCachePlatformDataComplete(TSet& CachedPlatformDataObjects,TSet& PendingCachePlatformDataObjects, + TArray TargetPlatforms) +{ + SCOPED_NAMED_EVENT_TEXT("WaitObjectsCachePlatformDataComplete",FColor::Red); + // FExecTimeRecoder WaitObjectsCachePlatformDataCompleteTimer(TEXT("WaitObjectsCachePlatformDataComplete")); + if (GShaderCompilingManager) + { + // Wait for all shaders to finish compiling + UFlibShaderCodeLibraryHelper::WaitShaderCompilingComplete(); + } + UFlibHotPatcherCoreHelper::WaitDistanceFieldAsyncQueueComplete(); + + { + SCOPED_NAMED_EVENT_TEXT("FlushAsyncLoading And WaitingAsyncTasks",FColor::Red); + FlushAsyncLoading(); + UE_LOG(LogHotPatcherCoreHelper, Display, TEXT("Waiting for async tasks...")); + FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread); + } + + { + SCOPED_NAMED_EVENT_TEXT("WaitCachePlatformDataComplete",FColor::Red); + while(!!PendingCachePlatformDataObjects.Num()) + { + TSet CachedObjects; + for(auto Object:PendingCachePlatformDataObjects) + { + bool bAllPlatformDataLoaded = true; + for (const ITargetPlatform* TargetPlatform : TargetPlatforms) + { + // check cache status + if (!Object->IsCachedCookedPlatformDataLoaded(TargetPlatform)) + { + bAllPlatformDataLoaded = false; + break; + }else{ + if(GCookLog) + { + UE_LOG(LogHotPatcherCoreHelper,Log,TEXT("PreCached ExportObj %s for %s"),*Object->GetFullName(),*TargetPlatform->PlatformName()); + } + } + } + // add to cached set + if (bAllPlatformDataLoaded) + { + CachedPlatformDataObjects.Add(Object); + CachedObjects.Add(Object); + } + } + + // remove cached + for(auto CachedObject:CachedObjects) + { + PendingCachePlatformDataObjects.Remove(CachedObject); + } + + if(!!PendingCachePlatformDataObjects.Num()) + { + #if ENGINE_MAJOR_VERSION > 4 + // call ProcessAsyncTasks instead of pure wait using Sleep + while (FAssetCompilingManager::Get().GetNumRemainingAssets() > 0) + { + // Process any asynchronous Asset compile results that are ready, limit execution time + FAssetCompilingManager::Get().ProcessAsyncTasks(true); + } + #else + FPlatformProcess::Sleep(0.1f); + #endif // ENGINE_MAJOR_VERSION > 4 + GLog->Flush(); + } + } + } + // UFlibHotPatcherCoreHelper::WaitForAsyncFileWrites(); +} + +uint32 UFlibHotPatcherCoreHelper::GetCookSaveFlag(UPackage* Package, bool bUnversioned, bool bStorageConcurrent, + bool CookLinkerDiff) +{ + uint32 SaveFlags = SAVE_KeepGUID | SAVE_Async| SAVE_ComputeHash | (bUnversioned ? SAVE_Unversioned : 0); + +#if ENGINE_MAJOR_VERSION >4 || ENGINE_MINOR_VERSION >25 + // bool CookLinkerDiff = false; + if(CookLinkerDiff) + { + SaveFlags |= SAVE_CompareLinker; + } +#endif + if (bStorageConcurrent) + { + SaveFlags |= SAVE_Concurrent; + } + return SaveFlags; +} + +EObjectFlags UFlibHotPatcherCoreHelper::GetObjectFlagForCooked(UPackage* Package) +{ + EObjectFlags CookedFlags = RF_Public; + if(UWorld::FindWorldInPackage(Package)) + { + CookedFlags = RF_NoFlags; + } + return CookedFlags; +} + + +void UFlibHotPatcherCoreHelper::SaveGlobalShaderMapFiles(const TArrayView& Platforms,const FString& BaseOutputDir) +{ + // we don't support this behavior + for (int32 Index = 0; Index < Platforms.Num(); Index++) + { + // make sure global shaders are up to date! + TArray Files; + FShaderRecompileData RecompileData; + RecompileData.PlatformName = Platforms[Index]->PlatformName(); + // Compile for all platforms + RecompileData.ShaderPlatform = SP_NumPlatforms; + RecompileData.ModifiedFiles = &Files; + RecompileData.MeshMaterialMaps = NULL; + + check( IsInGameThread() ); + + FString OutputDir = FPaths::Combine(BaseOutputDir,Platforms[Index]->PlatformName()); + +#if ENGINE_MAJOR_VERSION > 4 + TArray GlobalShaderMap; + RecompileData.CommandType = ODSCRecompileCommand::Global; + RecompileData.GlobalShaderMap = &GlobalShaderMap; + RecompileShadersForRemote(RecompileData, OutputDir); +#else + RecompileShadersForRemote + (RecompileData.PlatformName, + RecompileData.ShaderPlatform == -1 ? SP_NumPlatforms : (EShaderPlatform)RecompileData.ShaderPlatform, //-V547 + OutputDir, + RecompileData.MaterialsToLoad, +#if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 26 + RecompileData.ShadersToRecompile, +#endif +#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION < 25 + RecompileData.SerializedShaderResources, +#endif + RecompileData.MeshMaterialMaps, + RecompileData.ModifiedFiles); +#endif + } +} + +FString UFlibHotPatcherCoreHelper::GetSavePackageResultStr(ESavePackageResult Result) +{ + FString Str; + + switch (Result) + { + case ESavePackageResult::Success: + { + Str = TEXT("Success"); + break; + } + case ESavePackageResult::Canceled: + { + Str = TEXT("Canceled"); + break; + } + case ESavePackageResult::Error: + { + Str = TEXT("Error"); + break; + } + case ESavePackageResult::DifferentContent: + { + Str = TEXT("DifferentContent"); + break; + } + case ESavePackageResult::GenerateStub: + { + Str = TEXT("GenerateStub"); + break; + } + case ESavePackageResult::MissingFile: + { + Str = TEXT("MissingFile"); + break; + } + case ESavePackageResult::ReplaceCompletely: + { + Str = TEXT("ReplaceCompletely"); + break; + } + case ESavePackageResult::ContainsEditorOnlyData: + { + Str = TEXT("ContainsEditorOnlyData"); + break; + } + case ESavePackageResult::ReferencedOnlyByEditorOnlyData: + { + Str = TEXT("ReferencedOnlyByEditorOnlyData"); + break; + } + } + return Str; +} + +void UFlibHotPatcherCoreHelper::AdaptorOldVersionConfig(FAssetScanConfig& ScanConfig, const FString& JsonContent) +{ + THotPatcherTemplateHelper::TDeserializeJsonStringAsStruct(JsonContent,ScanConfig); +} + + +bool UFlibHotPatcherCoreHelper::GetIniPlatformName(const FString& InPlatformName, FString& OutIniPlatformName) +{ + bool bStatus = false; + ITargetPlatformManagerModule& TPM = GetTargetPlatformManagerRef(); + const TArray& TargetPlatforms = TPM.GetTargetPlatforms(); + for (ITargetPlatform *TargetPlatformIns : TargetPlatforms) + { + FString PlatformName = TargetPlatformIns->PlatformName(); + if(PlatformName.Equals(InPlatformName)) + { + OutIniPlatformName = TargetPlatformIns->IniPlatformName(); + bStatus = true; + break; + } + } + return bStatus; +} + +#if GENERATE_CHUNKS_MANIFEST +#include "Commandlets/AssetRegistryGenerator.h" +#include "Commandlets/AssetRegistryGenerator.cpp" +#endif + +bool UFlibHotPatcherCoreHelper::SerializeChunksManifests(ITargetPlatform* TargetPlatform, const TSet& CookedPackageNames, const TSet& IgnorePackageNames, bool + bGenerateStreamingInstallManifest) +{ + bool bresult = true; +#if GENERATE_CHUNKS_MANIFEST +#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION < 26 + TUniquePtr TempSandboxFile = MakeUnique(false); +#else + TUniquePtr TempSandboxFile = FSandboxPlatformFile::Create(false); +#endif + FString PlatformSandboxDir = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectSavedDir(),TEXT("Cooked"),TargetPlatform->PlatformName())); + // Use SandboxFile to do path conversion to properly handle sandbox paths (outside of standard paths in particular). + TempSandboxFile->Initialize(&FPlatformFileManager::Get().GetPlatformFile(), *FString::Printf(TEXT("-sandbox=\"%s\""), *PlatformSandboxDir)); + TUniquePtr RegistryGenerator = MakeUnique(TargetPlatform); + RegistryGenerator->CleanManifestDirectories(); + RegistryGenerator->Initialize(TArray()); + RegistryGenerator->PreSave(CookedPackageNames); +#if ENGINE_MAJOR_VERSION > 4 + RegistryGenerator->FinalizeChunkIDs(CookedPackageNames, IgnorePackageNames, *TempSandboxFile, bGenerateStreamingInstallManifest); +#else + RegistryGenerator->BuildChunkManifest(CookedPackageNames, IgnorePackageNames, TempSandboxFile.Get(), bGenerateStreamingInstallManifest); +#endif +#if ENGINE_MAJOR_VERSION > 4 || (ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION > 26) + FString TempSandboxManifestDir = FPaths::Combine(PlatformSandboxDir,FApp::GetProjectName(),TEXT("Metadata") , TEXT("ChunkManifest")); + if(!FPaths::DirectoryExists(TempSandboxManifestDir)) + { + IFileManager::Get().MakeDirectory(*TempSandboxManifestDir, true); + } +#endif + bresult = RegistryGenerator->SaveManifests( +#if ENGINE_MAJOR_VERSION > 4 + *TempSandboxFile +#else +TempSandboxFile.Get() +#endif + ); +#endif + return bresult; +} + +TArray UFlibHotPatcherCoreHelper::GetClassesByNames(const TArray& ClassesNames) +{ + SCOPED_NAMED_EVENT_TEXT("GetClassesByNames",FColor::Red); + TArray result; + for(const auto& ClassesName:ClassesNames) + { + for (TObjectIterator Itt; Itt; ++Itt) + { + if((*Itt)->GetName().Equals(ClassesName.ToString())) + { + result.Add(*Itt); + break; + } + } + } + return result; +} + +TArray UFlibHotPatcherCoreHelper::GetAllMaterialClasses() +{ + SCOPED_NAMED_EVENT_TEXT("GetAllMaterialClasses",FColor::Red); + TArray Classes; + TArray ParentClassesName = { + // materials + TEXT("MaterialExpression"), + TEXT("MaterialParameterCollection"), + TEXT("MaterialFunctionInterface"), + TEXT("Material"), + TEXT("MaterialInterface"), + }; + for(auto& ParentClass:GetClassesByNames(ParentClassesName)) + { + Classes.Append(UFlibHotPatcherCoreHelper::GetDerivedClasses(ParentClass,true,true)); + } + 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()) + { + result.Add(Class->GetFName()); + } + return result; +} + +TArray UFlibHotPatcherCoreHelper::GetPreCacheClasses() +{ + SCOPED_NAMED_EVENT_TEXT("GetPreCacheClasses",FColor::Red); + TArray Classes; + + TArray ParentClassesName = { + // textures + TEXT("Texture"), + TEXT("PaperSprite"), + // material + // TEXT("MaterialExpression"), + // TEXT("MaterialParameterCollection"), + // TEXT("MaterialFunctionInterface"), + // TEXT("MaterialInterface"), + // other + TEXT("PhysicsAsset"), + TEXT("PhysicalMaterial"), + TEXT("StaticMesh"), + // curve + TEXT("CurveFloat"), + TEXT("CurveVector"), + TEXT("CurveLinearColor"), + // skeletal and animation + TEXT("Skeleton"), + TEXT("SkeletalMesh"), + TEXT("AnimSequence"), + TEXT("BlendSpace1D"), + TEXT("BlendSpace"), + TEXT("AnimMontage"), + TEXT("AnimComposite"), + // blueprint + TEXT("UserDefinedStruct"), + TEXT("Blueprint"), + // sound + TEXT("SoundWave"), + // particles + TEXT("FXSystemAsset"), + // large ref asset + TEXT("ActorSequence"), + TEXT("LevelSequence"), + TEXT("World") + }; + + for(auto& ParentClass:UFlibHotPatcherCoreHelper::GetClassesByNames(ParentClassesName)) + { + Classes.Append(UFlibHotPatcherCoreHelper::GetDerivedClasses(ParentClass,true,true)); + } + Classes.Append(UFlibHotPatcherCoreHelper::GetAllMaterialClasses()); + TSet Results; + for(const auto& Class:Classes) + { + Results.Add(Class); + } + 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); +} + +FString UFlibHotPatcherCoreHelper::GetPlatformsStr(TArray Platforms) +{ + return UFlibPatchParserHelper::GetPlatformsStr(Platforms); +} + +#include "DistanceFieldAtlas.h" +void UFlibHotPatcherCoreHelper::WaitDistanceFieldAsyncQueueComplete() +{ + SCOPED_NAMED_EVENT_TEXT("WaitDistanceFieldAsyncQueueComplete",FColor::Red); + if (GDistanceFieldAsyncQueue) + { + UE_LOG(LogHotPatcherCoreHelper, Display, TEXT("Waiting for distance field async operations...")); + GDistanceFieldAsyncQueue->BlockUntilAllBuildsComplete(); + } +} \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Private/HotPatcherCore.cpp b/HotPatcher/Source/HotPatcherCore/Private/HotPatcherCore.cpp new file mode 100644 index 0000000..9a0e3de --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Private/HotPatcherCore.cpp @@ -0,0 +1,80 @@ +// Copyright 2019 Lipeng Zha, Inc. All Rights Reserved. + +#include "HotPatcherCore.h" +#include "HotPatcherSettings.h" + +// ENGINE HEADER + +#include "AssetToolsModule.h" +#include "CommandletHelper.h" +#include "ContentBrowserModule.h" +#include "IContentBrowserSingleton.h" +#include "Misc/MessageDialog.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "DesktopPlatformModule.h" +#include "FlibHotPatcherCoreHelper.h" +#include "HotPatcherLog.h" +#include "ISettingsModule.h" +#include "LevelEditor.h" +#include "HAL/FileManager.h" +#include "Interfaces/IPluginManager.h" +#include "Kismet/KismetTextLibrary.h" +#include "PakFileUtilities.h" + +#include "Cooker/MultiCooker/SingleCookerProxy.h" +#include "CreatePatch/PatcherProxy.h" +#include "Settings/ProjectPackagingSettings.h" +#include "ThreadUtils/FProcWorkerThread.hpp" + + +FExportPatchSettings* GPatchSettings = nullptr; +FExportReleaseSettings* GReleaseSettings = nullptr; +bool GCookLog = (bool)ENABLE_COOK_LOG; +FString GToolName = TOOL_NAME; +int32 GToolMainVersion = CURRENT_VERSION_ID; +int32 GToolPatchVersion = CURRENT_PATCH_ID; +FString GRemoteVersionFile = REMOTE_VERSION_FILE; + +static const FName HotPatcherTabName("HotPatcher"); + +#define LOCTEXT_NAMESPACE "FHotPatcherCoreModule" + +void ReceiveOutputMsg(FProcWorkerThread* Worker,const FString& InMsg) +{ + FString FindItem(TEXT("Display:")); + int32 Index= InMsg.Len() - InMsg.Find(FindItem)- FindItem.Len(); + if (InMsg.Contains(TEXT("Error:"))) + { + UE_LOG(LogHotPatcher, Error, TEXT("%s"), *InMsg); + } + else if (InMsg.Contains(TEXT("Warning:"))) + { + UE_LOG(LogHotPatcher, Warning, TEXT("%s"), *InMsg); + } + else + { + UE_LOG(LogHotPatcher, Display, TEXT("%s"), *InMsg.Right(Index)); + } +} + +FHotPatcherCoreModule& FHotPatcherCoreModule::Get() +{ + FHotPatcherCoreModule& Module = FModuleManager::GetModuleChecked("HotPatcherCore"); + return Module; +} + + +void FHotPatcherCoreModule::StartupModule() +{ + FParse::Bool(FCommandLine::Get(),TEXT("-cooklog"),GCookLog); + UE_LOG(LogHotPatcher,Log,TEXT("GCookLog is %s!!!"),GCookLog ? TEXT("TRUE"): TEXT("FALSE")); +} + +void FHotPatcherCoreModule::ShutdownModule() +{ + +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FHotPatcherCoreModule, HotPatcherCore) \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Private/HotPatcherDelegates.cpp b/HotPatcher/Source/HotPatcherCore/Private/HotPatcherDelegates.cpp new file mode 100644 index 0000000..dfe0b95 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Private/HotPatcherDelegates.cpp @@ -0,0 +1,8 @@ +#include "HotPatcherDelegates.h" + +FHotPatcherDelegates& FHotPatcherDelegates::Get() +{ + // return the singleton object + static FHotPatcherDelegates Singleton; + return Singleton; +} diff --git a/HotPatcher/Source/HotPatcherCore/Private/ShaderLibUtils/FCookShaderCollectionProxy.cpp b/HotPatcher/Source/HotPatcherCore/Private/ShaderLibUtils/FCookShaderCollectionProxy.cpp new file mode 100644 index 0000000..77c4a72 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Private/ShaderLibUtils/FCookShaderCollectionProxy.cpp @@ -0,0 +1,87 @@ +#include "Cooker/MultiCooker/FCookShaderCollectionProxy.h" +#include "FlibHotPatcherCoreHelper.h" +#include "Cooker/MultiCooker/FlibHotCookerHelper.h" +#include "ShaderLibUtils//FlibShaderCodeLibraryHelper.h" +#include "Interfaces/ITargetPlatform.h" +#include "Resources/Version.h" + +FCookShaderCollectionProxy::FCookShaderCollectionProxy(const TArray& InPlatformNames,const FString& InLibraryName,bool bInShareShader,bool InIsNative,bool bInMaster,const FString& InSaveBaseDir) +:PlatformNames(InPlatformNames),LibraryName(InLibraryName),bShareShader(bInShareShader),bIsNative(InIsNative),bMaster(bInMaster),SaveBaseDir(InSaveBaseDir){} + +FCookShaderCollectionProxy::~FCookShaderCollectionProxy(){} + +void FCookShaderCollectionProxy::Init() +{ + if(bShareShader) + { + SHADER_COOKER_CLASS::InitForCooking(bIsNative); + for(const auto& PlatformName:PlatformNames) + { + ITargetPlatform* TargetPlatform = UFlibHotPatcherCoreHelper::GetPlatformByName(PlatformName); + TargetPlatforms.AddUnique(TargetPlatform); + TArray ShaderFormats = UFlibShaderCodeLibraryHelper::GetShaderFormatsByTargetPlatform(TargetPlatform); + if (ShaderFormats.Num() > 0) + { + #if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 25 + TArray ShaderFormatsWithStableKeys = UFlibShaderCodeLibraryHelper::GetShaderFormatsWithStableKeys(ShaderFormats); + #else + #if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION > 22 + TArray> ShaderFormatsWithStableKeys; + for (FName& Format : ShaderFormats) + { + ShaderFormatsWithStableKeys.Push(MakeTuple(Format, true)); + } + #else + TArray ShaderFormatsWithStableKeys = ShaderFormats; + #endif + #endif + SHADER_COOKER_CLASS::CookShaderFormats(ShaderFormatsWithStableKeys); + } + } + FShaderCodeLibrary::OpenLibrary(LibraryName,TEXT("")); + } +} + + + +void FCookShaderCollectionProxy::Shutdown() +{ + SCOPED_NAMED_EVENT_TEXT("FCookShaderCollectionProxy::Shutdown",FColor::Red); + if(bShareShader) + { + for(const auto& TargetPlatform:TargetPlatforms) + { + FString PlatformName = TargetPlatform->PlatformName(); + bSuccessed = UFlibShaderCodeLibraryHelper::SaveShaderLibrary(TargetPlatform, NULL, LibraryName,SaveBaseDir,bMaster); +#if ENGINE_MAJOR_VERSION < 5 && ENGINE_MINOR_VERSION <= 26 + if(bIsNative) + { + FString ShaderCodeDir = FPaths::Combine(SaveBaseDir,PlatformName); + bSuccessed = bSuccessed && FShaderCodeLibrary::PackageNativeShaderLibrary(ShaderCodeDir,UFlibShaderCodeLibraryHelper::GetShaderFormatsByTargetPlatform(TargetPlatform)); + } +#endif + // rename StarterContent_SF_METAL.0.metallib to startercontent_sf_metal.0.metallib + if(bSuccessed && bIsNative && UFlibHotCookerHelper::IsAppleMetalPlatform(TargetPlatform)) + { + TArray FoundShaderLibs = UFlibShaderCodeLibraryHelper::FindCookedShaderLibByPlatform(PlatformName,FPaths::Combine(SaveBaseDir,TargetPlatform->PlatformName())); + for(const auto& Shaderlib:FoundShaderLibs) + { + if(Shaderlib.EndsWith(TEXT("metallib"),ESearchCase::IgnoreCase) || Shaderlib.EndsWith(TEXT("metalmap"),ESearchCase::IgnoreCase)) + { + FString Path = FPaths::GetPath(Shaderlib); + FString Name = FPaths::GetBaseFilename(Shaderlib,true); + FString Extersion = FPaths::GetExtension(Shaderlib,true); + Name = FString::Printf(TEXT("%s%s"),*Name,*Extersion); + Name.ToLowerInline(); + if(!Shaderlib.EndsWith(Name,ESearchCase::CaseSensitive)) + { + IFileManager::Get().Move(*FPaths::Combine(Path,Name),*Shaderlib,false); + } + } + } + } + } + FShaderCodeLibrary::CloseLibrary(LibraryName); + FShaderCodeLibrary::Shutdown(); + } +} diff --git a/HotPatcher/Source/HotPatcherCore/Private/ShaderLibUtils/FlibShaderCodeLibraryHelper.cpp b/HotPatcher/Source/HotPatcherCore/Private/ShaderLibUtils/FlibShaderCodeLibraryHelper.cpp new file mode 100644 index 0000000..4703775 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Private/ShaderLibUtils/FlibShaderCodeLibraryHelper.cpp @@ -0,0 +1,294 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "ShaderLibUtils//FlibShaderCodeLibraryHelper.h" +#include "HotPatcherLog.h" +#include "FlibHotPatcherCoreHelper.h" +#include "HotPatcherCore.h" + +#include "ShaderCompiler.h" +#include "IPlatformFileSandboxWrapper.h" +#include "Interfaces/IPluginManager.h" +#include "Interfaces/ITargetPlatform.h" +#include "Misc/EngineVersionComparison.h" + +#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(); +// } +// FMergeShaderCollectionProxy::~FMergeShaderCollectionProxy() +// { +// Shutdown(); +// } +// +// void FMergeShaderCollectionProxy::Init() +// { +// for(const auto& ShaderCodeFoemat:ShaderCodeFiles) +// { +// TArray ShaderFormatNames = UFlibShaderCodeLibraryHelper::GetShaderFormatsByTargetPlatform(ShaderCodeFoemat.Platform); +// +// for(const auto& ShaderFormatName:ShaderFormatNames) +// { +// EShaderPlatform ShaderPlatform= ::ShaderFormatToLegacyShaderPlatform(ShaderFormatName); +// +// if(ShaderCodeFoemat.ShaderCodeTypeFilesMap.Contains(ShaderFormatName.ToString())) +// { +// SHADER_COOKER_CLASS::InitForCooking(ShaderCodeFoemat.bIsNative); +// SHADER_COOKER_CLASS::InitForRuntime(ShaderPlatform); +// TArray CurrentPlatforomShaderTypeNames; +// { +// TArray PlatforomShaderTypeNames; +// ShaderCodeFoemat.ShaderCodeTypeFilesMap.GetKeys(PlatforomShaderTypeNames); +// for(const auto& Name:PlatforomShaderTypeNames) +// { +// CurrentPlatforomShaderTypeNames.AddUnique(*Name); +// } +// } +// +// #if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 25 +// TArray ShaderFormatsWithStableKeys = UFlibShaderCodeLibraryHelper::GetShaderFormatsWithStableKeys(CurrentPlatforomShaderTypeNames); +// #else +// TArray> ShaderFormatsWithStableKeys; +// for (FName& Format : ShaderFormatNames) +// { +// ShaderFormatsWithStableKeys.Push(MakeTuple(Format, true)); +// } +// #endif +// +// SHADER_COOKER_CLASS::CookShaderFormats(ShaderFormatsWithStableKeys); +// FShaderCodeFormatMap::FShaderFormatNameFiles ShaderFormatNameFiles = *ShaderCodeFoemat.ShaderCodeTypeFilesMap.Find(ShaderFormatName.ToString()); +// for(const auto& File:ShaderFormatNameFiles.Files) +// { +// FString Path = FPaths::GetPath(File); +// FShaderCodeLibrary::OpenLibrary(ShaderFormatNameFiles.ShaderName,Path); +// } +// if(!UFlibShaderCodeLibraryHelper::SaveShaderLibrary(ShaderCodeFoemat.Platform, NULL, FApp::GetProjectName(),ShaderCodeFoemat.SaveBaseDir,true)) +// { +// UE_LOG(LogHotPatcherCoreHelper,Display,TEXT("SaveShaderLibrary %s for %s Failed!"),*FApp::GetProjectName(),*ShaderCodeFoemat.Platform->PlatformName() ); +// } +// SHADER_COOKER_CLASS::Shutdown(); +// } +// } +// } +// } +// +// void FMergeShaderCollectionProxy::Shutdown() +// { +// FShaderCodeLibrary::Shutdown(); +// } + +#if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 25 +TArray UFlibShaderCodeLibraryHelper::GetShaderFormatsWithStableKeys( + const TArray& ShaderFormats,bool bNeedShaderStableKeys/* = true*/,bool bNeedsDeterministicOrder/* = true*/) +{ + TArray ShaderFormatsWithStableKeys; + for (const FName& Format : ShaderFormats) + { + SHADER_COOKER_CLASS::FShaderFormatDescriptor NewDesc; + NewDesc.ShaderFormat = Format; + NewDesc.bNeedsStableKeys = bNeedShaderStableKeys; + NewDesc.bNeedsDeterministicOrder = bNeedsDeterministicOrder; + ShaderFormatsWithStableKeys.Push(NewDesc); + } + return ShaderFormatsWithStableKeys; +} +#endif + +TArray UFlibShaderCodeLibraryHelper::GetShaderFormatsByTargetPlatform(ITargetPlatform* TargetPlatform) +{ + TArray ShaderFormats; + TargetPlatform->GetAllTargetedShaderFormats(ShaderFormats); + return ShaderFormats; +} + +FString UFlibShaderCodeLibraryHelper::GenerateShaderCodeLibraryName(FString const& Name, bool bIsIterateSharedBuild) +{ + FString ActualName = (!bIsIterateSharedBuild) ? Name : Name + TEXT("_SC"); + return ActualName; +} + +bool UFlibShaderCodeLibraryHelper::SaveShaderLibrary(const ITargetPlatform* TargetPlatform,TArray ShaderFormats, const FString& ShaderCodeDir,const FString& RootMetaDataPath, bool bMaster) +{ + bool bSaved = false; + // TargetPlatform->GetAllTargetedShaderFormats(ShaderFormats); + if (ShaderFormats.Num() > 0) + { + FString TargetPlatformName = TargetPlatform->PlatformName(); + TArray PlatformSCLCSVPaths;// = OutSCLCSVPaths.FindOrAdd(FName(TargetPlatformName)); + + #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) + ,bOutHasData +#endif + ); +#else + TArray> ChunkAssignments; + bSaved = FShaderCodeLibrary::SaveShaderCode(ShaderCodeDir, RootMetaDataPath, ShaderFormats, PlatformSCLCSVPaths, &ChunkAssignments); +#endif +#else + if(bMaster) + { + bSaved = FShaderCodeLibrary::SaveShaderCodeMaster(ShaderCodeDir, RootMetaDataPath, ShaderFormats, PlatformSCLCSVPaths); + } + else + { + bSaved = FShaderCodeLibrary::SaveShaderCodeChild(ShaderCodeDir, RootMetaDataPath, ShaderFormats); + } +#endif + } + return bSaved; +} + +bool UFlibShaderCodeLibraryHelper::SaveShaderLibrary(const ITargetPlatform* TargetPlatform, const TArray>* ChunkAssignments, FString const& Name, const FString& + SaveBaseDir, bool bMaster) +{ + bool bSaved = false; + FString ActualName = GenerateShaderCodeLibraryName(Name, false); + FString BasePath = FPaths::ProjectContentDir(); + + FString ShaderCodeDir = FPaths::Combine(SaveBaseDir,TargetPlatform->PlatformName()); + + const FString RootMetaDataPath = ShaderCodeDir / TEXT("Metadata") / TEXT("PipelineCaches"); + + // note that shader formats can be shared across the target platforms + TArray ShaderFormats; + TargetPlatform->GetAllTargetedShaderFormats(ShaderFormats); + if (ShaderFormats.Num() > 0) + { + bSaved = UFlibShaderCodeLibraryHelper::SaveShaderLibrary(TargetPlatform,ShaderFormats,ShaderCodeDir,RootMetaDataPath,bMaster); +// FString TargetPlatformName = TargetPlatform->PlatformName(); +// TArray PlatformSCLCSVPaths;// = OutSCLCSVPaths.FindOrAdd(FName(TargetPlatformName)); +// +// #if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 25 +// #if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 26 +// FString ErrorString; +// bSaved = SHADER_COOKER_CLASS::SaveShaderLibraryWithoutChunking(TargetPlatform, FApp::GetProjectName(), ShaderCodeDir, RootMetaDataPath, PlatformSCLCSVPaths, ErrorString); +// #else +// bSaved = FShaderCodeLibrary::SaveShaderCode(ShaderCodeDir, RootMetaDataPath, ShaderFormats, PlatformSCLCSVPaths, ChunkAssignments); +// #endif +// #else +// if(bMaster) +// { +// bSaved = FShaderCodeLibrary::SaveShaderCodeMaster(ShaderCodeDir, RootMetaDataPath, ShaderFormats, PlatformSCLCSVPaths); +// } +// else +// { +// bSaved = FShaderCodeLibrary::SaveShaderCodeChild(ShaderCodeDir, RootMetaDataPath, ShaderFormats); +// } +// #endif + } + return bSaved; +} + + +TArray UFlibShaderCodeLibraryHelper::FindCookedShaderLibByShaderFrmat(const FString& ShaderFormatName,const FString& Directory) +{ + TArray result; + TArray FoundShaderFiles; + IFileManager::Get().FindFiles(FoundShaderFiles,*Directory,TEXT("ushaderbytecode")); + + FString FormatExtersion = FString::Printf(TEXT("*%s*.ushaderbytecode"),*ShaderFormatName); + for(const auto& ShaderFile:FoundShaderFiles) + { + if(ShaderFile.MatchesWildcard(FormatExtersion)) + { + result.Add(ShaderFile); + } + } + return result; +} + +TArray UFlibShaderCodeLibraryHelper::FindCookedShaderLibByPlatform(const FString& PlatfomName,const FString& Directory, bool bRecursive) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibShaderCodeLibraryHelper::FindCookedShaderLibByPlatform",FColor::Red); + TArray FoundFiles; + auto GetMetalShaderFormatLambda = [](const FString& Directory,const FString& Extersion, bool bRecursive) + { + TArray FoundMetallibFiles; + if(bRecursive) + { + IFileManager::Get().FindFilesRecursive(FoundMetallibFiles,*Directory,*Extersion,true,false, false); + } + else + { + IFileManager::Get().FindFiles(FoundMetallibFiles,*Directory,*Extersion); + } + return FoundMetallibFiles; + }; + + if(PlatfomName.StartsWith(TEXT("IOS"),ESearchCase::IgnoreCase) || PlatfomName.StartsWith(TEXT("Mac"),ESearchCase::IgnoreCase)) + { + FoundFiles.Append(GetMetalShaderFormatLambda(Directory,TEXT("metallib"),bRecursive)); + FoundFiles.Append(GetMetalShaderFormatLambda(Directory,TEXT("metalmap"),bRecursive)); + } + + if(!FoundFiles.Num()) + { + FoundFiles.Append(GetMetalShaderFormatLambda(Directory,TEXT("ushaderbytecode"),bRecursive)); + } + for(auto& File:FoundFiles) + { + File = FPaths::Combine(Directory,File); + } + return FoundFiles; +} + +void UFlibShaderCodeLibraryHelper::WaitShaderCompilingComplete() +{ + // Wait for all shaders to finish compiling + if (GShaderCompilingManager) + { + SCOPED_NAMED_EVENT_TEXT("WaitShaderCompileComplete",FColor::Red); + while(GShaderCompilingManager->IsCompiling()) + { + GShaderCompilingManager->ProcessAsyncResults(false, false); + // int32 CurrentNumRemaingingJobs = GShaderCompilingManager->GetNumRemainingJobs(); + // if(GCookLog && LastRemainingJob != CurrentNumRemaingingJobs) + // { + // 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(); + } +} + +void UFlibShaderCodeLibraryHelper::CleanShaderWorkerDir() +{ + if (FPaths::DirectoryExists(FPaths::ShaderWorkingDir()) && !IFileManager::Get().DeleteDirectory(*FPaths::ShaderWorkingDir(), false, true)) + { + 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 new file mode 100644 index 0000000..04dcdd5 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Public/CommandletBase/CommandletHelper.h @@ -0,0 +1,31 @@ +#pragma once + +#include "CoreMinimal.h" +#include "ETargetPlatform.h" + +#define PATCHER_CONFIG_PARAM_NAME TEXT("-config=") +#define ADD_PATCH_PLATFORMS TEXT("AddPatchPlatforms") +#define TARGET_PLATFORMS_OVERRIDE TEXT("TargetPlatformsOverride") + +DECLARE_LOG_CATEGORY_EXTERN(LogHotPatcherCommandlet, All, All); + +namespace CommandletHelper +{ + HOTPATCHERCORE_API void ReceiveMsg(const FString& InMsgType,const FString& InMsg); + HOTPATCHERCORE_API void ReceiveShowMsg(const FString& InMsg); + + HOTPATCHERCORE_API TArray ParserPatchConfigByCommandline(const FString& Commandline,const FString& Token); + + HOTPATCHERCORE_API TArray ParserPlatforms(const FString& Commandline, const FString& Token); + + HOTPATCHERCORE_API TArray ParserPatchFilters(const FString& Commandline,const FString& FilterName); + + HOTPATCHERCORE_API void MainTick(TFunction IsRequestExit); + + HOTPATCHERCORE_API bool GetCommandletArg(const FString& Token,FString& OutValue); + + HOTPATCHERCORE_API bool IsCookCommandlet(); + HOTPATCHERCORE_API TArray GetCookCommandletTargetPlatforms(); + HOTPATCHERCORE_API TArray GetCookCommandletTargetPlatformName(); + HOTPATCHERCORE_API void ModifyTargetPlatforms(const FString& InParams,const FString& InToken,TArray& OutTargetPlatforms,bool Replace); +} diff --git a/HotPatcher/Source/HotPatcherCore/Public/CommandletBase/HotPatcherCommandletBase.h b/HotPatcher/Source/HotPatcherCore/Public/CommandletBase/HotPatcherCommandletBase.h new file mode 100644 index 0000000..6daa4c0 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Public/CommandletBase/HotPatcherCommandletBase.h @@ -0,0 +1,30 @@ +#pragma once + +#include "FCountServerlessWrapper.h" +#include "Commandlets/Commandlet.h" +#include "HotPatcherCommandletBase.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogHotPatcherCommandletBase, All, All); + +UCLASS() +class HOTPATCHERCORE_API UHotPatcherCommandletBase :public UCommandlet +{ + GENERATED_BODY() + +public: + virtual int32 Main(const FString& Params)override; + virtual bool IsSkipObject(UObject* Object){ return false; } + virtual bool IsSkipPackage(UPackage* Package){ return false; } + virtual FString GetCmdletName()const { return TEXT("HotPatcherCmdletBase"); } + + static FString GetCrashDir(); + static void CleanCrashDir(); +protected: + void OnHandleSystemError(); +protected: + void Update(const FString& Params); + void MaybeMarkPackageAsAlreadyLoaded(UPackage* Package); + TSharedPtr ObjectTrackerTagCleaner; + FString CmdConfigPath; + TSharedPtr Counter; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Public/Cooker/HotPatcherCookerSettingBase.h b/HotPatcher/Source/HotPatcherCore/Public/Cooker/HotPatcherCookerSettingBase.h new file mode 100644 index 0000000..401d7f6 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Public/Cooker/HotPatcherCookerSettingBase.h @@ -0,0 +1,11 @@ +#pragma once +// engine +#include "CoreMinimal.h" +#include "Engine/EngineTypes.h" +#include "HotPatcherCookerSettingBase.generated.h" + +USTRUCT(BlueprintType) +struct HOTPATCHERCORE_API FHotPatcherCookerSettingBase: public FPatcherEntitySettingBase +{ + GENERATED_USTRUCT_BODY() +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Public/Cooker/MultiCooker/FCookShaderCollectionProxy.h b/HotPatcher/Source/HotPatcherCore/Public/Cooker/MultiCooker/FCookShaderCollectionProxy.h new file mode 100644 index 0000000..18bc783 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Public/Cooker/MultiCooker/FCookShaderCollectionProxy.h @@ -0,0 +1,24 @@ +#pragma once +#include "Resources/Version.h" +#include "ShaderCodeLibrary.h" +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Misc/AES.h" + +struct FCookShaderCollectionProxy +{ + FCookShaderCollectionProxy(const TArray& InPlatformNames,const FString& InLibraryName,bool bShareShader,bool InIsNative,bool bInMaster,const FString& InSaveBaseDir); + virtual ~FCookShaderCollectionProxy(); + virtual void Init(); + virtual void Shutdown(); + virtual bool IsSuccessed()const { return bSuccessed; } +private: + TArray TargetPlatforms; + TArray PlatformNames; + FString LibraryName; + bool bShareShader; + bool bIsNative; + bool bMaster; + FString SaveBaseDir; + bool bSuccessed = false; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Public/Cooker/MultiCooker/FSingleCookerSettings.h b/HotPatcher/Source/HotPatcherCore/Public/Cooker/MultiCooker/FSingleCookerSettings.h new file mode 100644 index 0000000..30c3863 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Public/Cooker/MultiCooker/FSingleCookerSettings.h @@ -0,0 +1,150 @@ +// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. + +#pragma once + +// project header +#include "HotPatcherLog.h" +#include "FlibPatchParserHelper.h" +#include "HotPatcherLog.h" +#include "Cooker/HotPatcherCookerSettingBase.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 "FSingleCookerSettings.generated.h" + +USTRUCT(BlueprintType) +struct HOTPATCHERCORE_API FCookerShaderOptions +{ + GENERATED_BODY() + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bSharedShaderLibrary = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bNativeShader = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cooker",meta=(EditCondition="bSharedShaderLibrary")) + bool bMergeShaderLibrary = false; +}; + +USTRUCT(BlueprintType) +struct HOTPATCHERCORE_API FSingleCookerSettings:public FHotPatcherCookerSettingBase +{ + GENERATED_BODY() +public: + FSingleCookerSettings(); + UPROPERTY(EditAnywhere,BlueprintReadWrite) + FString MissionName; + UPROPERTY(EditAnywhere,BlueprintReadWrite) + int32 MissionID = -1; + UPROPERTY(EditAnywhere,BlueprintReadWrite) + bool bShaderCooker = false; + UPROPERTY(EditAnywhere,BlueprintReadWrite) + FString ShaderLibName; + + UPROPERTY(EditAnywhere,BlueprintReadWrite) + TArray CookAssets; + + // skip loaded assets when cooking(package path) + UPROPERTY(EditAnywhere,BlueprintReadWrite) + TSet SkipLoadedAssets; + + // Directories or asset package path + UPROPERTY(EditAnywhere,BlueprintReadWrite) + TArray SkipCookContents; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TArray ForceSkipClasses; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cooker") + TArray CookTargetPlatforms; + + // track load asset when cooking + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cooker") + bool bPackageTracker = true; + // cook load in cooking assets by SingleCookder side + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cooker",meta=(EditCondition="bPackageTracker")) + bool bCookPackageTrackerAssets = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cooker") + FCookerShaderOptions ShaderOptions; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cooker") + bool bSerializeAssetRegistry = false; + // UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cooker") + // FAssetRegistryOptions SerializeAssetRegistryOptions; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cooker") + FIoStoreSettings IoStoreSettings; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cooker") + bool bForceCookInOneFrame = false; + FORCEINLINE int32 GetNumberOfAssetsPerFrame()const{ return NumberOfAssetsPerFrame; } + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cooker",meta=(EditCondition="!bForceCookInOneFrame",ClampMin=0)) + int32 NumberOfAssetsPerFrame = 100; + TMap OverrideNumberOfAssetsPerFrame; + FORCEINLINE const TMap& GetOverrideNumberOfAssetsPerFrame()const{ return OverrideNumberOfAssetsPerFrame; } + + // UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cooker") + bool bAsyncLoad = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cooker",meta=(EditCondition="!bAsyncLoad")) + bool bPreGeneratePlatformData = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cooker",meta=(EditCondition="bPreGeneratePlatformData")) + bool bWaitEachAssetCompleted = true; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cooker",meta=(EditCondition="bPreGeneratePlatformData")) + bool bConcurrentSave = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Debug") + bool bDisplayConfig = false; + + FString GetStorageCookedAbsDir()const; + UPROPERTY(EditAnywhere,BlueprintReadWrite) + FString StorageCookedDir; + + FString GetStorageMetadataAbsDir()const; + UPROPERTY(EditAnywhere,BlueprintReadWrite) + FString StorageMetadataDir; + + // UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "SavePackageContext") +#if WITH_PACKAGE_CONTEXT + bool bOverrideSavePackageContext = false; + TMap> PlatformSavePackageContexts; +#endif + bool IsSkipAsset(const FString& PackageName); +}; + +USTRUCT(BlueprintType) +struct FPackagePathSet +{ + GENERATED_BODY() + + UPROPERTY() + TSet PackagePaths; +}; +// +// USTRUCT(BlueprintType) +// struct HOTPATCHERCORE_API FAssetsCollection +// { +// GENERATED_BODY() +// UPROPERTY(EditAnywhere,BlueprintReadWrite) +// ETargetPlatform TargetPlatform = ETargetPlatform::None; +// UPROPERTY(EditAnywhere,BlueprintReadWrite) +// TArray Assets; +// }; + +USTRUCT(BlueprintType) +struct HOTPATCHERCORE_API FCookerFailedCollection +{ + GENERATED_BODY() +public: + UPROPERTY(EditAnywhere,BlueprintReadWrite) + FString MissionName; + UPROPERTY(EditAnywhere,BlueprintReadWrite) + int32 MissionID = -1; + UPROPERTY(EditAnywhere,BlueprintReadWrite) + TMap CookFailedAssets; +}; diff --git a/HotPatcher/Source/HotPatcherCore/Public/Cooker/MultiCooker/FlibHotCookerHelper.h b/HotPatcher/Source/HotPatcherCore/Public/Cooker/MultiCooker/FlibHotCookerHelper.h new file mode 100644 index 0000000..1abce9f --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Public/Cooker/MultiCooker/FlibHotCookerHelper.h @@ -0,0 +1,33 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "Resources/Version.h" +#include "CoreMinimal.h" +#include "ETargetPlatform.h" +#include "Cooker/MultiCooker/FCookShaderCollectionProxy.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "FlibHotCookerHelper.generated.h" + +/** + * + */ +UCLASS() +class HOTPATCHERCORE_API UFlibHotCookerHelper : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() +public: + static FString GetCookedDir(); + static FString GetCookerBaseDir(); + // static FString GetCookerProcConfigPath(const FString& MissionName,int32 MissionID); + static FString GetCookerProcFailedResultPath(const FString& BaseDir,const FString& MissionName, int32 MissionID); + static FString GetProfilingCmd(); + static TSharedPtr CreateCookShaderCollectionProxyByPlatform( + const FString& ShaderLibraryName, + TArray Platforms, + bool bShareShader, + bool bNativeShader, + bool bMaster, + const FString& InSavePath, + bool bCleanSavePath = true); + static bool IsAppleMetalPlatform(ITargetPlatform* TargetPlatform); +}; diff --git a/HotPatcher/Source/HotPatcherCore/Public/Cooker/MultiCooker/SingleCookerProxy.h b/HotPatcher/Source/HotPatcherCore/Public/Cooker/MultiCooker/SingleCookerProxy.h new file mode 100644 index 0000000..1d0370f --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Public/Cooker/MultiCooker/SingleCookerProxy.h @@ -0,0 +1,195 @@ +#pragma once +#include "FSingleCookerSettings.h" +#include "Cooker/HotPatcherCookerSettingBase.h" +#include "CreatePatch/HotPatcherProxyBase.h" +#include "HotPatcherBaseTypes.h" +#include "BaseTypes/FPackageTracker.h" +// engine header +#include "Templates/SharedPointer.h" +#include "Containers/Queue.h" +#include "TickableEditorObject.h" +#include "ThreadUtils/FThreadUtils.hpp" +#include "SingleCookerProxy.generated.h" + + + +bool IsAlwayPostLoadClasses(UPackage* Package, UObject* Object); + +struct FFreezePackageTracker : public FPackageTrackerBase +{ +public: + FFreezePackageTracker(const TSet& InCookerAssetsSet,const TSet& InAllAssets):CookerAssetsSet(InCookerAssetsSet),AllAssetsSet(InAllAssets){} + + virtual ~FFreezePackageTracker(){} + + virtual void NotifyUObjectCreated(const class UObjectBase *Object, int32 Index) override; + virtual void NotifyUObjectDeleted(const UObjectBase* Object, int32 Index) override + { + auto ObjectOuter = const_cast(static_cast(Object)); + if(FreezeObjects.Contains(ObjectOuter)) + { + FreezeObjects.Remove(ObjectOuter); + } + } + bool IsFreezed(UObject* Object)const + { + return FreezeObjects.Contains(Object); + } +protected: + TSet CookerAssetsSet; + TSet AllAssetsSet; + TSet FreezeObjects; + TMultiMap> PackageObjectsMap; + // TArray FreezeObjects; +}; + +struct FOtherCookerPackageTracker : public FPackageTrackerBase +{ + FOtherCookerPackageTracker(const TSet& InCookerAssets,const TSet& InAllCookerAssets):CookerAssets(InCookerAssets),AllCookerAssets(InAllCookerAssets){}; + + virtual ~FOtherCookerPackageTracker(){} + FORCEINLINE virtual void OnPackageCreated(UPackage* Package) override + { + FName AssetPathName = FName(*Package->GetPathName()); + if(!CookerAssets.Contains(AssetPathName) && AllCookerAssets.Contains(AssetPathName)) + { + LoadOtherCookerAssets.Add(AssetPathName); + UE_LOG(LogHotPatcher,Display,TEXT("[OtherCookerPackageTracker] %s "),*AssetPathName.ToString()); + } + } + FORCEINLINE const TSet& GetLoadOtherCookerAssets()const { return LoadOtherCookerAssets; } +protected: + TSet CookerAssets; + TSet AllCookerAssets; + TSet LoadOtherCookerAssets; +}; + +DECLARE_MULTICAST_DELEGATE(FSingleCookerEvent); +DECLARE_MULTICAST_DELEGATE_TwoParams(FSingleCookActionEvent,const FSoftObjectPath&,ETargetPlatform); +DECLARE_MULTICAST_DELEGATE_ThreeParams(FSingleCookResultEvent,const FSoftObjectPath&,ETargetPlatform,ESavePackageResult); + +USTRUCT() +struct FCookCluster +{ + GENERATED_BODY() + UPROPERTY() + TArray AssetDetails; + UPROPERTY() + TArray Platforms; + UPROPERTY() + bool bPreGeneratePlatformData = false; + + + FORCEINLINE_DEBUGGABLE TArray AsSoftObjectPaths()const + { + TArray SoftObjectPaths; + for(const auto& AssetDetail:AssetDetails) + { + if(AssetDetail.IsValid()) + { + SoftObjectPaths.Emplace(AssetDetail.PackagePath.ToString()); + } + } + return SoftObjectPaths; + } + FCookActionCallback CookActionCallback; +}; + + +UCLASS() +class HOTPATCHERCORE_API USingleCookerProxy:public UHotPatcherProxyBase, public FTickableEditorObject +{ + GENERATED_BODY() +public: + virtual void Init(FPatcherEntitySettingBase* InSetting)override; + virtual void Shutdown() override; + + virtual bool DoExport()override; + bool IsFinsihed(); + bool HasError(); + virtual FSingleCookerSettings* GetSettingObject()override {return (FSingleCookerSettings*)(Setting);}; + +public: // base func + virtual void Tick(float DeltaTime) override; + TStatId GetStatId() const override; + bool IsTickable() const override; + +public: // core interface + int32 MakeCookQueue(FCookCluster& InCluser); + void PreGeneratePlatformData(const FCookCluster& CookCluster); + void CleanClusterCachedPlatformData(const FCookCluster& CookCluster); + void ExecCookCluster(const FCookCluster& Cluster); + FCookerFailedCollection& GetCookFailedAssetsCollection(){return CookFailedAssetsCollection;}; + void CleanOldCooked(const FString& CookBaseDir,const TArray& ObjectPaths,const TArray& CookPlatforms); + // cook classes order + TArray GetPreCacheClasses()const; + +public: // callback + FCookActionResultEvent GetOnPackageSavedCallback(); + FCookActionEvent GetOnCookAssetBeginCallback(); + +public: // packae tracker interface + TSet GetCookerAssets(); + TSet GetAdditionalAssets(); + FCookCluster GetPackageTrackerAsCluster(); + FORCEINLINE_DEBUGGABLE TSet& GetPaendingCookAssetsSet(){ return PaendingCookAssetsSet; } + + TArray& GetPlatformCookAssetOrders(ETargetPlatform Platform); + +protected: // on asset cooked call func + void OnAssetCookedHandle(const FSoftObjectPath& PackagePath,ETargetPlatform Platform,ESavePackageResult Result); + +protected: // metadata func + void BulkDataManifest(); + void IoStoreManifest(); + void InitShaderLibConllections(); + void ShutdowShaderLibCollections(); + +private: // package context +#if WITH_PACKAGE_CONTEXT + FORCEINLINE TMap>& GetPlatformSavePackageContexts() {return PlatformSavePackageContexts;} + TMap GetPlatformSavePackageContextsRaw(); + TMap GetPlatformSavePackageContextsNameMapping(); + TMap> PlatformSavePackageContexts; +#endif + +public: // static function + static void DumpCluster(const FCookCluster& CookCluster, bool bWriteToLog); + +public: + int32 GetClassAssetNumOfPerCluster(UClass* Class); + +public: // delegate + FSingleCookerEvent OnCookBegin; + FSingleCookerEvent OnCookFinished; + FSingleCookActionEvent OnCookAssetBegin; + FSingleCookResultEvent OnAssetCooked; + +private: // cook assets manager + FCookCluster GlobalCluser; + TQueue CookCluserQueue; + FCookerFailedCollection CookFailedAssetsCollection; + +private: // metadate + TSharedPtr PlatformCookShaderCollection; + TMap> CookAssetOrders; + +private: // package tracker + TSet PaendingCookAssetsSet; + TSharedPtr PackageTracker; + TSharedPtr OtherCookerPackageTracker; + TSharedPtr FreezePackageTracker; + + FPackagePathSet ExixtPackagePathSet; + +private: // worker flow control + /** Critical section for synchronizing access to sink. */ + mutable FCriticalSection SynchronizationObject; + TSharedPtr WaitThreadWorker; + bool IsAlready()const{ return bAlready; } + bool bAlready = false; + +private: + int32 ClusterCount = 0; + int32 CookedClusterCount = 0; +}; diff --git a/HotPatcher/Source/HotPatcherCore/Public/CreatePatch/HotPatcherProxyBase.h b/HotPatcher/Source/HotPatcherCore/Public/CreatePatch/HotPatcherProxyBase.h new file mode 100644 index 0000000..9bf2799 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Public/CreatePatch/HotPatcherProxyBase.h @@ -0,0 +1,86 @@ +#pragma once +// project header +#include "CreatePatch/HotPatcherSettingBase.h" +#include "HotPatcherLog.h" +#include "CreatePatch/HotPatcherContext.h" +#include "CreatePatch/TimeRecorder.h" +#include "HotPatcherDelegates.h" + +// engine header +#include "CoreMinimal.h" +#include "HotPatcherProxyBase.generated.h" + + + +UCLASS() +class HOTPATCHERCORE_API UHotPatcherProxyBase : public UObject +{ +public: + GENERATED_BODY() + + + virtual void Init(FPatcherEntitySettingBase* InSetting); + virtual void Shutdown(); + FORCEINLINE virtual bool DoExport(){return false;}; + FORCEINLINE virtual FPatcherEntitySettingBase* GetSettingObject(){return Setting;}; + IAssetRegistry* GetAssetRegistry()const { return AssetRegistry; } +protected: + FORCEINLINE virtual void SetProxySettings(FPatcherEntitySettingBase* InSetting) + { + Setting = InSetting; + } + const TMap& GetPlatformNameMapping(){ return PlatformNameMapping; } +public: +#if WITH_PACKAGE_CONTEXT + // virtual void InitPlatformPackageContexts(); + FORCEINLINE TMap> GetPlatformSavePackageContexts()const {return PlatformSavePackageContexts;} + FORCEINLINE TMap GetPlatformSavePackageContextsRaw()const; + TMap> PlatformSavePackageContexts; +#endif + +public: + FExportPakProcess OnPaking; + FExportPakShowMsg OnShowMsg; +protected: + FPatcherEntitySettingBase* Setting; + IAssetRegistry* AssetRegistry = NULL; + TMap PlatformNameMapping; +}; + +inline void UHotPatcherProxyBase::Init(FPatcherEntitySettingBase* InSetting) +{ + SetProxySettings(InSetting); + + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + AssetRegistry = &AssetRegistryModule.Get(); + + UEnum* UEnumIns = THotPatcherTemplateHelper::GetUEnum(); + for (int64 EnumIndex = 0;EnumIndex < UEnumIns->GetMaxEnumValue();++EnumIndex) + { + if(UEnumIns->IsValidEnumValue(EnumIndex)) + { + FName EnumtorName = UEnumIns->GetNameByValue(EnumIndex); + PlatformNameMapping.Add((ETargetPlatform)EnumIndex,EnumtorName); + } + } +} + +inline void UHotPatcherProxyBase::Shutdown() +{ + AssetRegistry = nullptr; +} + + +#if WITH_PACKAGE_CONTEXT +FORCEINLINE TMap UHotPatcherProxyBase::GetPlatformSavePackageContextsRaw() const +{ + TMap result; + TArray Keys; + GetPlatformSavePackageContexts().GetKeys(Keys); + for(const auto& Key:Keys) + { + result.Add(Key,GetPlatformSavePackageContexts().Find(Key)->Get()); + } + return result; +} +#endif diff --git a/HotPatcher/Source/HotPatcherCore/Public/CreatePatch/IPatchableInterface.h b/HotPatcher/Source/HotPatcherCore/Public/CreatePatch/IPatchableInterface.h new file mode 100644 index 0000000..4a2e655 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Public/CreatePatch/IPatchableInterface.h @@ -0,0 +1,18 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" + +class IPatchableInterface +{ + + // Add interface functions to this class. This is the class that will be inherited to implement this interface. +public: + virtual void ImportConfig()=0; + virtual void ImportProjectConfig()=0; + virtual void ExportConfig()const=0; + virtual void ResetConfig() = 0; + virtual void DoGenerate()=0; + virtual FString GetMissionName()=0; +}; diff --git a/HotPatcher/Source/HotPatcherCore/Public/CreatePatch/PatcherProxy.h b/HotPatcher/Source/HotPatcherCore/Public/CreatePatch/PatcherProxy.h new file mode 100644 index 0000000..510c341 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Public/CreatePatch/PatcherProxy.h @@ -0,0 +1,54 @@ +#pragma once + +#include "CreatePatch/FExportPatchSettings.h" +#include "FPatchVersionDiff.h" +#include "HotPatcherProxyBase.h" +#include "ThreadUtils/FThreadUtils.hpp" + +// engine header +#include "Interfaces/ITargetPlatformManagerModule.h" +#include "Interfaces/ITargetPlatform.h" +#include "Interfaces/ITargetPlatform.h" +#include "Templates/SharedPointer.h" +#include "IDetailsView.h" +#include "PropertyEditorModule.h" +#include "Cooker/MultiCooker/FCookShaderCollectionProxy.h" +#include "Widgets/Text/SMultiLineEditableText.h" + +#include "PatcherProxy.generated.h" + +using FPatchWorkerType = TFunction; +using FPatchWorkers = TMap; + +DECLARE_MULTICAST_DELEGATE_FourParams(FOnPakListGenerated,FHotPatcherPatchContext&,FChunkInfo&,ETargetPlatform,TArray&); +DECLARE_MULTICAST_DELEGATE_FourParams(FAddPatchWorkerEvent,FHotPatcherPatchContext&,FChunkInfo&,ETargetPlatform,TArray&); + + +UCLASS() +class HOTPATCHERCORE_API UPatcherProxy:public UHotPatcherProxyBase +{ + GENERATED_UCLASS_BODY() +public: + virtual void Init(FPatcherEntitySettingBase* InSetting)override; + virtual void Shutdown() override; + + virtual bool DoExport() override; + virtual FExportPatchSettings* GetSettingObject()override{ return (FExportPatchSettings*)Setting; } + bool CanExportPatch() const; + + FORCEINLINE_DEBUGGABLE void AddPatchWorker(const FString& WorkerName,const FPatchWorkerType& Worker) + { + PatchWorkers.Add(WorkerName,Worker); + } + FORCEINLINE const FPatchWorkers& GetPatchWorkers()const{ return PatchWorkers; } + FORCEINLINE FPatherResult& GetPatcherResult(){ return PatcherResult; } + +public: + FOnPakListGenerated OnPakListGenerated; + +protected: + FPatchWorkers PatchWorkers; +private: + TSharedPtr PatchContext; + FPatherResult PatcherResult; +}; diff --git a/HotPatcher/Source/HotPatcherCore/Public/CreatePatch/ReleaseProxy.h b/HotPatcher/Source/HotPatcherCore/Public/CreatePatch/ReleaseProxy.h new file mode 100644 index 0000000..a293bd4 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Public/CreatePatch/ReleaseProxy.h @@ -0,0 +1,22 @@ +#pragma once +#include "CreatePatch/FExportReleaseSettings.h" +#include "HotPatcherProxyBase.h" +// ENGINE HEADER +#include "CoreMinimal.h" +#include "CoreGlobals.h" +#include "ReleaseProxy.generated.h" + + +UCLASS() +class HOTPATCHERCORE_API UReleaseProxy:public UHotPatcherProxyBase +{ +public: + GENERATED_BODY() + + virtual bool DoExport() override ; + FORCEINLINE bool IsRunningCommandlet()const{return ::IsRunningCommandlet();} + FORCEINLINE virtual FExportReleaseSettings* GetSettingObject()override { return (FExportReleaseSettings*)Setting; } + +private: + FExportReleaseSettings* ExportReleaseSettings; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Public/CreatePatch/ReleaseSettingsDetails.h b/HotPatcher/Source/HotPatcherCore/Public/CreatePatch/ReleaseSettingsDetails.h new file mode 100644 index 0000000..cd3f37f --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Public/CreatePatch/ReleaseSettingsDetails.h @@ -0,0 +1,15 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "IDetailCustomization.h" + +class FReleaseSettingsDetails : public IDetailCustomization +{ +public: + /** Makes a new instance of this detail layout class for a specific detail view requesting it */ + static TSharedRef MakeInstance(); + + /** IDetailCustomization interface */ + virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Public/FCountServerlessWrapper.h b/HotPatcher/Source/HotPatcherCore/Public/FCountServerlessWrapper.h new file mode 100644 index 0000000..d01a582 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Public/FCountServerlessWrapper.h @@ -0,0 +1,52 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Interfaces/IHttpRequest.h" +#include "Interfaces/IHttpResponse.h" + +struct HOTPATCHERCORE_API FProjectVersionDesc +{ + FString ProjectName; + FString EngineVersion; + FString PluginVersion; + FString GameName; + FString UserName; +}; + +struct HOTPATCHERCORE_API FServerRequestInfo +{ + FString Host; + FString AppId; + FString Key; +}; + +struct HOTPATCHERCORE_API FCountServerlessWrapper +{ + void Init(const FServerRequestInfo& InRequestInfo,const FProjectVersionDesc& InDesc) + { + RequestInfo = InRequestInfo; + Desc = InDesc; + } + ~FCountServerlessWrapper(); + + void Processor(); + +public: + static FProjectVersionDesc MakeCurrentProject(); + static FServerRequestInfo MakeServerRequestInfo(); +protected: + void RequestObjectID(); + void OnObjectIdReceived( FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess); + + FHttpRequestPtr UpdateToServer(const FProjectVersionDesc& Desc,const FString& ObjectID); + void OnUpdateToServerReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess); + FHttpRequestPtr CreateToServer(const FProjectVersionDesc& Desc); + void CreateToServerReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess); + + FString Decode(const FString& Encode); +protected: + FServerRequestInfo RequestInfo; + FProjectVersionDesc Desc; + FHttpRequestPtr ObjectIDRequest; + FHttpRequestPtr ToServerRequest; +}; diff --git a/HotPatcher/Source/HotPatcherCore/Public/FlibHotPatcherCoreHelper.h b/HotPatcher/Source/HotPatcherCore/Public/FlibHotPatcherCoreHelper.h new file mode 100644 index 0000000..37e535f --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Public/FlibHotPatcherCoreHelper.h @@ -0,0 +1,296 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "BaseTypes/FExternFileInfo.h" +#include "BaseTypes/FExternDirectoryInfo.h" +#include "BaseTypes/FPatcherSpecifyAsset.h" +#include "BaseTypes/HotPatcherBaseTypes.h" +#include "BaseTypes/FHotPatcherVersion.h" +#include "BaseTypes/FChunkInfo.h" +#include "BaseTypes/ETargetPlatform.h" +#include "CreatePatch/FExportPatchSettings.h" + +// engine header +#include "Templates/SharedPointer.h" +#include "Dom/JsonObject.h" + +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" + +#if WITH_PACKAGE_CONTEXT + #include "UObject/SavePackage.h" + #if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION > 25 + #include "Serialization/BulkDataManifest.h" + #endif +#endif + +#include "FlibHotPatcherCoreHelper.generated.h" + + +DECLARE_LOG_CATEGORY_EXTERN(LogHotPatcherCoreHelper, Log, All); + +struct FExportPatchSettings; + + + +struct FExecTimeRecoder +{ + FExecTimeRecoder(const FString& InDispalyStr):DispalyStr(InDispalyStr) + { + BeginTime = FDateTime::Now(); + } + ~FExecTimeRecoder() + { + EndTime = FDateTime::Now(); + FTimespan ClusterExecTime = EndTime - BeginTime; + UE_LOG(LogHotPatcherCoreHelper,Display,TEXT("%s Time : %fs"),*DispalyStr,ClusterExecTime.GetTotalSeconds()) + } + FString DispalyStr; + FDateTime BeginTime; + FDateTime EndTime; +}; + +struct FProjectPackageAssetCollection +{ + TArray DirectoryPaths; + TArray NeverCookPaths; + TArray NeedCookPackages; + TArray NeverCookPackages; +}; + +/** + * + */ +UCLASS() +class HOTPATCHERCORE_API UFlibHotPatcherCoreHelper : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() +public: + + UFUNCTION(BlueprintCallable, Category = "HotPatcherCore") + static TArray GetAllCookOption(); + + static void CheckInvalidCookFilesByAssetDependenciesInfo( + const FString& InProjectAbsDir, + const FString& OverrideCookedDir, + const FString& InPlatformName, + const FAssetDependenciesInfo& InAssetDependencies, + TArray& OutValidAssets, + TArray& OutInvalidAssets); + + static FChunkInfo MakeChunkFromPatchSettings(struct FExportPatchSettings const* InPatchSetting); + static FChunkInfo MakeChunkFromPatchVerison(const FHotPatcherVersion& InPatchVersion); + static FString GetAssetCookedSavePath(const FString& BaseDir, const FString PacakgeName, const FString& Platform); + + static FString GetProjectCookedDir(); +#if WITH_PACKAGE_CONTEXT + static FSavePackageContext* CreateSaveContext(const ITargetPlatform* TargetPlatform,bool bUseZenLoader,const FString& OverrideCookedDir); + static TMap> CreatePlatformsPackageContexts(const TArray& Platforms,bool bIoStore,const FString& OverrideCookedDir); + static bool SavePlatformBulkDataManifest(TMap>&PlatformSavePackageContexts,ETargetPlatform Platform); +#endif + //UFUNCTION(BlueprintCallable) + static void CookAssets( + const TArray& Assets, + const TArray& Platforms, + FCookActionCallback CookActionCallback, +#if WITH_PACKAGE_CONTEXT + class TMap PlatformSavePackageContext = TMap< + ETargetPlatform, FSavePackageContext*>{}, +#endif + const FString& InSavePath = FPaths::Combine(FPaths::ConvertRelativePathToFull(FPaths::ProjectSavedDir()), + TEXT("Cooked")) + ); + static bool CookPackage( + const FSoftObjectPath& AssetObjectPath, + TMap CookPlatforms, + FCookActionCallback CookActionCallback, +#if WITH_PACKAGE_CONTEXT + class TMap PlatformSavePackageContext = TMap{}, +#endif + const FString& InSavePath = FPaths::Combine(FPaths::ConvertRelativePathToFull(FPaths::ProjectSavedDir()),TEXT("Cooked")), + bool bStorageConcurrent = false + ); + static bool CookPackage( + UPackage* Package, + TMap CookPlatforms, + FCookActionCallback CookActionCallback, +#if WITH_PACKAGE_CONTEXT + class TMap PlatformSavePackageContext, +#endif + const FString& InSavePath, + bool bStorageConcurrent + ); + + static bool CookPackage( + UPackage* Package, + TMap CookPlatforms, + FCookActionCallback CookActionCallback, +#if WITH_PACKAGE_CONTEXT + class TMap PlatformSavePackageContext, +#endif + const TMap& CookedPlatformSavePaths, + bool bStorageConcurrent + ); + + static void CookChunkAssets( + TArray Assets, + const TArray& Platforms, + FCookActionCallback CookActionCallback, +#if WITH_PACKAGE_CONTEXT + class TMap PlatformSavePackageContext = TMap{}, +#endif + const FString& InSavePath = FPaths::Combine(FPaths::ConvertRelativePathToFull(FPaths::ProjectSavedDir()),TEXT("Cooked")) + ); + + static ITargetPlatform* GetTargetPlatformByName(const FString& PlatformName); + static TArray GetTargetPlatformsByNames(const TArray& PlatformNames); + + UFUNCTION(BlueprintCallable, Category = "HotPatcherCore") + static FString GetUnrealPakBinary(); + UFUNCTION(BlueprintCallable, Category = "HotPatcherCore") + static FString GetUECmdBinary(); + + static FProcHandle DoUnrealPak(TArray UnrealPakCommandletOptions, bool block); + + static FString GetMetadataDir(const FString& ProjectDir,const FString& ProjectName,ETargetPlatform Platform); + static void CleanDefaultMetadataCache(const TArray& TargetPlatforms); + + static void BackupMetadataDir(const FString& ProjectDir,const FString& ProjectName,const TArray& Platforms,const FString& OutDir); + static void BackupProjectConfigDir(const FString& ProjectDir,const FString& OutDir); + + static FString ReleaseSummary(const FHotPatcherVersion& NewVersion); + static FString PatchSummary(const FPatchVersionDiff& DiffInfo); + + + 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); + + // WindowsNoEditor to Windows + static FString Conv2IniPlatform(const FString& Platform); + static TArray GetSupportPlatforms(); + + static FString GetEncryptSettingsCommandlineOptions(const FPakEncryptSettings& EncryptSettings,const FString& PlatformName); + + static ITargetPlatform* GetPlatformByName(const FString& Name); + + /* + * 0x1 Add + * 0x2 Modyfy + */ + static void AnalysisDependenciesAssets(const FHotPatcherVersion& Base, const FHotPatcherVersion& New,FPatchVersionDiff& PakDiff,UClass* SearchClass,TArray DependenciesClass,bool bRecursive,int32 flags = 0x1|0x2); + static void AnalysisWidgetTree(const FHotPatcherVersion& Base, const FHotPatcherVersion& New,FPatchVersionDiff& PakDiff,int32 flags = 0x1|0x2); + static void AnalysisMaterialInstance(const FHotPatcherVersion& Base, const FHotPatcherVersion& New,FPatchVersionDiff& PakDiff,int32 flags= 0x1|0x2); + + static FPatchVersionDiff DiffPatchVersionWithPatchSetting(const struct FExportPatchSettings& PatchSetting, const FHotPatcherVersion& Base, const FHotPatcherVersion& New); + + // CurrenrVersionChunk中的过滤器会进行依赖分析,TotalChunk的不会,目的是让用户可以自己控制某个文件夹打包到哪个Pak里,而不会对该文件夹下的资源进行依赖分析 + static FChunkAssetDescribe DiffChunkWithPatchSetting( + const struct FExportPatchSettings& PatchSetting, + const FChunkInfo& CurrentVersionChunk, + const FChunkInfo& TotalChunk + //,TMap& ScanedCaches + ); + static FChunkAssetDescribe DiffChunkByBaseVersionWithPatchSetting( + const struct FExportPatchSettings& PatchSetting, + const FChunkInfo& CurrentVersionChunk, + const FChunkInfo& TotalChunk, + const FHotPatcherVersion& BaseVersion + //,TMap& ScanedCaches + ); + static bool SerializeAssetRegistryByDetails(IAssetRegistry* AssetRegistry, const FString& PlatformName, const TArray& AssetDetails, const FString& SavePath, FAssetRegistrySerializationOptions SaveOptions); + static bool SerializeAssetRegistryByDetails(IAssetRegistry* AssetRegistry, const FString& PlatformName, const TArray& AssetDetails, const FString& SavePath); + static bool SerializeAssetRegistry(IAssetRegistry* AssetRegistry, const FString& PlatformName, const TArray& PackagePaths, const FString& SavePath, FAssetRegistrySerializationOptions + SaveOptions); + + static FHotPatcherVersion MakeNewRelease(const FHotPatcherVersion& InBaseVersion, const FHotPatcherVersion& InCurrentVersion, FExportPatchSettings* InPatchSettings); + static FHotPatcherVersion MakeNewReleaseByDiff(const FHotPatcherVersion& InBaseVersion, const FPatchVersionDiff& InDiff, FExportPatchSettings* InPatchSettings = NULL); + +public: + // In: "C:\Users\lipengzha\Documents\UnrealProjects\Blank425\Intermediate\Staging\Blank425.upluginmanifest" "../../../Blank425/Plugins/Blank425.upluginmanifest + // To: upluginmanifest + static FString GetPakCommandExtersion(const FString& InCommand); + + // [Pak] + // +ExtensionsToNotUsePluginCompression=uplugin + // +ExtensionsToNotUsePluginCompression=upluginmanifest + // +ExtensionsToNotUsePluginCompression=uproject + // +ExtensionsToNotUsePluginCompression=ini + // +ExtensionsToNotUsePluginCompression=icu + // +ExtensionsToNotUsePluginCompression=res + static TArray GetExtensionsToNotUsePluginCompressionByGConfig(); + + static void AppendPakCommandOptions( + TArray& OriginCommands, + const TArray& Options, + bool bAppendAllMatch, + const TArray& AppendFileExtersions, + const TArray& IgnoreFormats, + const TArray& InIgnoreOptions); + + static FProjectPackageAssetCollection ImportProjectSettingsPackages(); + + static void WaitDistanceFieldAsyncQueueComplete(); + static void WaitForAsyncFileWrites(); + static void WaitDDCComplete(); + static bool IsCanCookPackage(const FString& LongPackageName); + + static void ImportProjectSettingsToScannerConfig(FAssetScanConfig& AssetScanConfig); + static void ImportProjectNotAssetDir(TArray& PlatformExternAssets, ETargetPlatform AddToTargetPlatform); + + static TArray GetProjectNotAssetDirConfig(); + + static void CacheForCookedPlatformData( + const TArray& Packages, + TArray TargetPlatforms, + TSet& ProcessedObjs, + TSet& PendingCachePlatformDataObjects, + bool bStorageConcurrent = false, + bool bWaitComplete = false, + TFunction OnPreCacheObjectWithOuter=nullptr + ); + static void CacheForCookedPlatformData( + const TArray& ObjectPaths, + TArray TargetPlatforms, + TSet& ProcessedObjs, + TSet& PendingCachePlatformDataObjects, + bool bStorageConcurrent, + bool bWaitComplete = false, + TFunction OnPreCacheObjectWithOuter = nullptr + ); + + static void WaitObjectsCachePlatformDataComplete(TSet& CachedPlatformDataObjects,TSet& PendingCachePlatformDataObjects,TArray TargetPlatforms); + + static uint32 GetCookSaveFlag(UPackage* Package,bool bUnversioned = true,bool bStorageConcurrent = false,bool CookLinkerDiff = false); + static EObjectFlags GetObjectFlagForCooked(UPackage* Package); + + static TArray GetReferenceRecursivelyByClassName(const FAssetDetail& AssetDetail,const TArray& AssetTypeNames,const TArray& RefType,bool bRecursive = true); + + static TArray GetDerivedClasses(UClass* BaseClass,bool bRecursive,bool bContainSelf = false); + + static void DeleteDirectory(const FString& Dir); + + static int32 GetMemoryMappingAlignment(const FString& PlatformName); + + static void SaveGlobalShaderMapFiles(const TArrayView& Platforms,const FString& BaseOutputDir); + + static FString GetSavePackageResultStr(ESavePackageResult Result); + + 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 + 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(); + static FString GetPlatformsStr(TArray Platforms); +}; diff --git a/HotPatcher/Source/HotPatcherCore/Public/HotPatcherCore.h b/HotPatcher/Source/HotPatcherCore/Public/HotPatcherCore.h new file mode 100644 index 0000000..0a95ee1 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Public/HotPatcherCore.h @@ -0,0 +1,31 @@ +// Copyright 2019 Lipeng Zha, Inc. All Rights Reserved. + +#pragma once + +#include "HotPatcherSettings.h" +// engine header +#include "CoreMinimal.h" + +HOTPATCHERCORE_API extern struct FExportPatchSettings* GPatchSettings; +HOTPATCHERCORE_API extern struct FExportReleaseSettings* GReleaseSettings; +extern HOTPATCHERCORE_API bool GCookLog; +extern HOTPATCHERCORE_API FString GToolName; +extern HOTPATCHERCORE_API FString GRemoteVersionFile; +extern HOTPATCHERCORE_API int32 GToolMainVersion; +extern HOTPATCHERCORE_API int32 GToolPatchVersion; + +HOTPATCHERCORE_API void ReceiveOutputMsg(class FProcWorkerThread* Worker,const FString& InMsg); + +DECLARE_MULTICAST_DELEGATE_TwoParams(FNotificationEvent,FText,const FString&) + + +class HOTPATCHERCORE_API FHotPatcherCoreModule : public IModuleInterface +{ +public: + static FHotPatcherCoreModule& Get(); + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + virtual int32 GetMainVersion()const{ return CURRENT_VERSION_ID; } + virtual int32 GetPatchVersion()const { return CURRENT_PATCH_ID; } +}; diff --git a/HotPatcher/Source/HotPatcherCore/Public/HotPatcherDelegates.h b/HotPatcher/Source/HotPatcherCore/Public/HotPatcherDelegates.h new file mode 100644 index 0000000..a02d386 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Public/HotPatcherDelegates.h @@ -0,0 +1,15 @@ +#pragma once + +#include "CoreMinimal.h" +#include "GameDelegates.h" + +DECLARE_MULTICAST_DELEGATE_TwoParams(FNotificationEvent,FText,const FString&) + +class HOTPATCHERCORE_API FHotPatcherDelegates +{ +public: + /** Return a single FGameDelegates object */ + static FHotPatcherDelegates& Get(); + + DEFINE_GAME_DELEGATE_TYPED(NotifyFileGenerated,FNotificationEvent); +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Public/HotPatcherSettings.h b/HotPatcher/Source/HotPatcherCore/Public/HotPatcherSettings.h new file mode 100644 index 0000000..a1a4d66 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Public/HotPatcherSettings.h @@ -0,0 +1,79 @@ +#pragma once +#include "ETargetPlatform.h" +#include "CreatePatch/FExportPatchSettings.h" +#include "FlibPatchParserHelper.h" +#include "CoreMinimal.h" +#include "Kismet/KismetTextLibrary.h" +#include "HotPatcherSettings.generated.h" +#define LOCTEXT_NAMESPACE "UHotPatcherSettings" + +USTRUCT() +struct FPakExternalInfo +{ + GENERATED_BODY() + FPakExternalInfo()=default; + FPakExternalInfo(const FPakExternalInfo&)=default; + UPROPERTY(EditAnywhere) + FString PakName; + UPROPERTY(EditAnywhere) + TArray TargetPlatforms; + UPROPERTY(EditAnywhere) + FPlatformExternAssets AddExternAssetsToPlatform; +}; + +UCLASS(config = Game, defaultconfig) +class HOTPATCHERCORE_API UHotPatcherSettings:public UObject +{ + GENERATED_UCLASS_BODY() +public: + UPROPERTY(EditAnywhere, config, Category = "Editor") + bool bWhiteListCookInEditor = false; + UPROPERTY(EditAnywhere, config, Category = "Editor") + TArray PlatformWhitelists; + + FString GetTempSavedDir()const; + FString GetHPLSavedDir()const; + + UPROPERTY(EditAnywhere, config, Category = "ConfigTemplate") + FExportPatchSettings TempPatchSetting; + + UPROPERTY(EditAnywhere, config, Category = "Preset") + TArray PresetConfigs; + + UPROPERTY(EditAnywhere, config, Category = "Preview") + bool bPreviewTooltips = true; + UPROPERTY(EditAnywhere, config, Category = "Preview") + bool bExternalFilesCheck = false; + + UPROPERTY(config) + bool bServerlessCounter = true; + UPROPERTY(EditAnywhere, config, Category = "Advanced") + bool bServerlessCounterInCmdlet = false; +}; + + +FORCEINLINE FString UHotPatcherSettings::GetTempSavedDir()const +{ + return UFlibPatchParserHelper::ReplaceMark(TempPatchSetting.SavePath.Path); +} +FORCEINLINE UHotPatcherSettings::UHotPatcherSettings(const FObjectInitializer& Initializer):Super(Initializer) +{ + auto ResetTempSettings = [](FExportPatchSettings& InTempPatchSetting) + { + InTempPatchSetting.bByBaseVersion=false; + // TempPatchSetting.bStorageAssetDependencies = false; + InTempPatchSetting.bStorageDiffAnalysisResults=false; + InTempPatchSetting.bStorageDeletedAssetsToNewReleaseJson = false; + InTempPatchSetting.bStorageConfig = false; + InTempPatchSetting.bStorageNewRelease = false; + InTempPatchSetting.bStoragePakFileInfo = false; + InTempPatchSetting.bCookPatchAssets = true; + InTempPatchSetting.CookShaderOptions.bSharedShaderLibrary = false; + InTempPatchSetting.CookShaderOptions.bNativeShader = false; + InTempPatchSetting.EncryptSettings.bUseDefaultCryptoIni = true; + InTempPatchSetting.SavePath.Path = TEXT("[PROJECTDIR]/Saved/HotPatcher/Paks"); + }; + ResetTempSettings(TempPatchSetting); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Public/ShaderLibUtils/FlibShaderCodeLibraryHelper.h b/HotPatcher/Source/HotPatcherCore/Public/ShaderLibUtils/FlibShaderCodeLibraryHelper.h new file mode 100644 index 0000000..968ca31 --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Public/ShaderLibUtils/FlibShaderCodeLibraryHelper.h @@ -0,0 +1,83 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "Resources/Version.h" +#include "ShaderCodeLibrary.h" +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "FlibShaderCodeLibraryHelper.generated.h" + +#if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 26 + #define SHADER_COOKER_CLASS FShaderLibraryCooker +#else + #define SHADER_COOKER_CLASS FShaderCodeLibrary +#endif + +struct FShaderCodeFormatMap +{ + ITargetPlatform* Platform; + bool bIsNative; + FString SaveBaseDir; + struct FShaderFormatNameFiles + { + FString ShaderName; + TArray Files; + }; + // etc GLSL_ES3_1_ANDROID something files + // SF_METAL something files + TMap ShaderCodeTypeFilesMap; +}; +// +// struct FMergeShaderCollectionProxy +// { +// FMergeShaderCollectionProxy(const TArray& InShaderCodeFiles); +// virtual ~FMergeShaderCollectionProxy(); +// void Init(); +// void Shutdown(); +// private: +// TArray ShaderCodeFiles; +// }; + +/** + * + */ +UCLASS() +class HOTPATCHERCORE_API UFlibShaderCodeLibraryHelper : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() +public: +#if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 25 + static TArray GetShaderFormatsWithStableKeys(const TArray& ShaderFormats,bool bNeedShaderStableKeys = true,bool bNeedsDeterministicOrder = false); +#endif + static TArray GetShaderFormatsByTargetPlatform(ITargetPlatform* TargetPlatform); + static FString GenerateShaderCodeLibraryName(FString const& Name, bool bIsIterateSharedBuild); + static bool SaveShaderLibrary(const ITargetPlatform* TargetPlatform,TArray ShaderFormats, const FString& ShaderCodeDir,const FString& RootMetaDataPath, bool bMaster); + static bool SaveShaderLibrary(const ITargetPlatform* TargetPlatform, const TArray>* ChunkAssignments, FString const& Name, const FString& + SaveBaseDir, bool bMaster); + static TArray FindCookedShaderLibByPlatform(const FString& PlatfomName,const FString& Directory,bool bRecursive = false); + static TArray FindCookedShaderLibByShaderFrmat(const FString& ShaderFormatName,const FString& Directory); + + static void WaitShaderCompilingComplete(); + 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/ThreadUtils/FProcWorkerThread.hpp b/HotPatcher/Source/HotPatcherCore/Public/ThreadUtils/FProcWorkerThread.hpp new file mode 100644 index 0000000..8c4f7fa --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Public/ThreadUtils/FProcWorkerThread.hpp @@ -0,0 +1,141 @@ +#pragma once +#include "FThreadUtils.hpp" +#include "CoreMinimal.h" +#include "Misc/Paths.h" +#include "GenericPlatform/GenericPlatformProcess.h" + +class FProcWorkerThread; + +DECLARE_DELEGATE_TwoParams(FOutputMsgDelegate,FProcWorkerThread*,const FString&); +DECLARE_MULTICAST_DELEGATE_OneParam(FProcStatusDelegate,FProcWorkerThread*); + + +class FProcWorkerThread : public FThreadWorker +{ +public: + explicit FProcWorkerThread(const TCHAR *InThreadName,const FString& InProgramPath,const FString& InParams) + : FThreadWorker(InThreadName, []() {}), mProgramPath(InProgramPath), mPragramParams(InParams) + {} + + virtual uint32 Run()override + { + if (FPaths::FileExists(mProgramPath)) + { + FPlatformProcess::CreatePipe(mReadPipe, mWritePipe); + // std::cout << TCHAR_TO_ANSI(*mProgramPath) << " " << TCHAR_TO_ANSI(*mPragramParams) << std::endl; + + mProcessHandle = FPlatformProcess::CreateProc(*mProgramPath, *mPragramParams, false, true, true, &mProcessID, 1, NULL, mWritePipe,mReadPipe); + if (mProcessHandle.IsValid() && FPlatformProcess::IsApplicationRunning(mProcessID)) + { + if (ProcBeginDelegate.IsBound()) + ProcBeginDelegate.Broadcast(this); + } + + FString Line; + while (mProcessHandle.IsValid() && FPlatformProcess::IsApplicationRunning(mProcessID)) + { + FString NewLine = FPlatformProcess::ReadPipe(mReadPipe); + if (NewLine.Len() > 0) + { + // process the string to break it up in to lines + Line += NewLine; + TArray StringArray; + int32 count = Line.ParseIntoArray(StringArray, TEXT("\n"), true); + if (count > 1) + { + for (int32 Index = 0; Index < count - 1; ++Index) + { + StringArray[Index].TrimEndInline(); + ProcOutputMsgDelegate.ExecuteIfBound(this,StringArray[Index]); + } + Line = StringArray[count - 1]; + if (NewLine.EndsWith(TEXT("\n"))) + { + Line += TEXT("\n"); + } + } + } + FPlatformProcess::Sleep(0.2f); + } + + bool bRunSuccessfuly = false; + int32 ProcReturnCode; + if (FPlatformProcess::GetProcReturnCode(mProcessHandle,&ProcReturnCode)) + { + if (ProcReturnCode == 0) + { + bRunSuccessfuly = true; + } + } + ProcOutputMsgDelegate.ExecuteIfBound(this,FString::Printf(TEXT("ProcWorker %s return value %d."),*mThreadName,ProcReturnCode)); + + mThreadStatus = EThreadStatus::Completed; + if (bRunSuccessfuly) + { + if(ProcSuccessedDelegate.IsBound()) + { + ProcSuccessedDelegate.Broadcast(this); + } + } + else + { + if (ProcFaildDelegate.IsBound()) + { + ProcFaildDelegate.Broadcast(this); + } + } + } + return 0; + } + + virtual void Exit()override + { + FThreadWorker::Exit(); + } + + virtual void Cancel()override + { + if (GetThreadStatus() != EThreadStatus::Busy) + { + if (CancelDelegate.IsBound()) + CancelDelegate.Broadcast(); + return; + } + + mThreadStatus = EThreadStatus::Canceling; + if (mProcessHandle.IsValid() && FPlatformProcess::IsApplicationRunning(mProcessID)) + { + FPlatformProcess::TerminateProc(mProcessHandle, true); + + if (ProcFaildDelegate.IsBound()) + { + ProcFaildDelegate.Broadcast(this); + } + mProcessHandle.Reset(); + mProcessID = 0; + } + mThreadStatus = EThreadStatus::Canceled; + if (CancelDelegate.IsBound()) + { + CancelDelegate.Broadcast(); + } + } + + virtual uint32 GetProcesId()const { return mProcessID; } + virtual FProcHandle GetProcessHandle()const { return mProcessHandle; } + +public: + FProcStatusDelegate ProcBeginDelegate; + FProcStatusDelegate ProcSuccessedDelegate; + FProcStatusDelegate ProcFaildDelegate; + FOutputMsgDelegate ProcOutputMsgDelegate; + +private: + FRunnableThread* mThread; + FString mProgramPath; + FString mPragramParams; + void* mReadPipe; + void* mWritePipe; + uint32 mProcessID; + FProcHandle mProcessHandle; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherCore/Public/ThreadUtils/FThreadUtils.hpp b/HotPatcher/Source/HotPatcherCore/Public/ThreadUtils/FThreadUtils.hpp new file mode 100644 index 0000000..e6848ce --- /dev/null +++ b/HotPatcher/Source/HotPatcherCore/Public/ThreadUtils/FThreadUtils.hpp @@ -0,0 +1,83 @@ +#pragma once +#include "HAL/Runnable.h" +#include "HAL/RunnableThread.h" +DECLARE_MULTICAST_DELEGATE(FThreadWorkerStatusDelegate); + +namespace EThreadStatus +{ + enum Type + { + InActive, + + Busy, + + Canceling, + + Canceled, + + Completed + }; + +} +class FThreadWorker : public FRunnable +{ +public: + using FCallback = TFunction; + explicit FThreadWorker(const TCHAR *InThreadName, const FCallback& InRunFunc) + :mThreadName(InThreadName),mRunFunc(InRunFunc),mThreadStatus(EThreadStatus::InActive) + {} + + virtual void Execute() + { + if (GetThreadStatus() == EThreadStatus::InActive) + { + mThread = FRunnableThread::Create(this, *mThreadName); + if (mThread) + { + mThreadStatus = EThreadStatus::Busy; + } + } + } + virtual void Join() + { + mThread->WaitForCompletion(); + } + + virtual uint32 Run()override + { + mRunFunc(); + + return 0; + } + virtual void Stop()override + { + Cancel(); + } + virtual void Cancel() + { + mThreadStatus = EThreadStatus::Canceled; + } + virtual void Exit()override + { + mThreadStatus = EThreadStatus::Completed; + } + + virtual EThreadStatus::Type GetThreadStatus()const + { + return mThreadStatus; + } +public: + FThreadWorkerStatusDelegate CancelDelegate; + FORCEINLINE FString GetThreadName()const {return mThreadName;} + virtual bool IsCompleted()const { return GetThreadStatus() == EThreadStatus::Completed || GetThreadStatus() == EThreadStatus::Canceled;} +protected: + FString mThreadName; + FCallback mRunFunc; + FRunnableThread* mThread; + volatile EThreadStatus::Type mThreadStatus; + +private: + FThreadWorker(const FThreadWorker&) = delete; + FThreadWorker& operator=(const FThreadWorker&) = delete; + +}; diff --git a/HotPatcher/Source/HotPatcherEditor/HotPatcherEditor.Build.cs b/HotPatcher/Source/HotPatcherEditor/HotPatcherEditor.Build.cs new file mode 100644 index 0000000..fe9acd9 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/HotPatcherEditor.Build.cs @@ -0,0 +1,108 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System; +using System.Collections.Generic; +using System.IO; + +public class HotPatcherEditor : ModuleRules +{ + public HotPatcherEditor(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + bLegacyPublicIncludePaths = false; + OptimizeCode = CodeOptimization.InShippingBuildsOnly; + + PublicIncludePaths.AddRange(new string[] { }); + PrivateIncludePaths.AddRange(new string[] {}); + + PublicDependencyModuleNames.AddRange( + new string[] + { + "UnrealEd", + "UMG", + "UMGEditor", + "Core", + "Json", + "ContentBrowser", + "SandboxFile", + "JsonUtilities", + "TargetPlatform", + "DesktopPlatform", + "Projects", + "Settings", + "EditorStyle", + "HTTP", + "RHI", + "EngineSettings", + "AssetRegistry", + "PakFileUtilities", + "HotPatcherRuntime", + "BinariesPatchFeature", + "HotPatcherCore" + // ... add other public dependencies that you statically link with here ... + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "UnrealEd", + "Projects", + "DesktopPlatform", + "InputCore", + "LevelEditor", + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + "RenderCore" + // ... add private dependencies that you statically link with here ... + } + ); + + if (Target.Version.MajorVersion > 4 || Target.Version.MinorVersion > 23) + { + PublicDependencyModuleNames.AddRange(new string[]{ + "ToolMenus", + "TraceLog" + }); + } + + System.Func AddPublicDefinitions = (string MacroName,bool bEnable) => + { + PublicDefinitions.Add(string.Format("{0}={1}",MacroName, bEnable ? 1 : 0)); + return true; + }; + + AddPublicDefinitions("ENABLE_COOK_ENGINE_MAP", false); + AddPublicDefinitions("ENABLE_COOK_PLUGIN_MAP", false); + BuildVersion Version; + BuildVersion.TryRead(BuildVersion.GetDefaultFileName(), out Version); + AddPublicDefinitions("WITH_EDITOR_SECTION", Version.MajorVersion > 4 || Version.MinorVersion > 24); + + System.Console.WriteLine("MajorVersion {0} MinorVersion: {1} PatchVersion {2}",Target.Version.MajorVersion,Target.Version.MinorVersion,Target.Version.PatchVersion); + + PublicDefinitions.AddRange(new string[] + { + "ENABLE_ORIGINAL_COOKER=0" + }); + + PublicDefinitions.AddRange(new string[] + { + "ENABLE_UPDATER_CHECK=1" + }); + + bool bEnablePackageContext = true; + AddPublicDefinitions("WITH_PACKAGE_CONTEXT", (Version.MajorVersion > 4 || Version.MinorVersion > 23) && bEnablePackageContext); + if (Version.MajorVersion > 4 || Version.MinorVersion > 26) + { + PublicDependencyModuleNames.AddRange(new string[] + { + "IoStoreUtilities", + "UnrealEd" + }); + } + } +} diff --git a/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/IOriginalCookerChildWidget.h b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/IOriginalCookerChildWidget.h new file mode 100644 index 0000000..bcee803 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/IOriginalCookerChildWidget.h @@ -0,0 +1,30 @@ +#pragma once +#include "Dom/JsonObject.h" +#include "Templates/SharedPointer.h" +#include "Model/FHotPatcherContextBase.h" +#include "Model/FOriginalCookerContext.h" + +struct IOriginalCookerChildWidget +{ + virtual TSharedPtr SerializeAsJson()const {return nullptr;}; + virtual void DeSerializeFromJsonObj(TSharedPtrconst & InJsonObject){}; + virtual FString GetSerializeName()const { return TEXT(""); }; + virtual void Reset() {}; + virtual void SetContext(TSharedPtr InContext) + { + mContext = InContext; + } + TSharedPtr GetContext()const { return mContext; }; + virtual FOriginalCookerContext* GetCookerContextPtr()const + { + FOriginalCookerContext* Ptr = nullptr; + if(GetContext().IsValid()) + { + Ptr = (FOriginalCookerContext*)mContext.Get(); + } + return Ptr; + } + virtual ~IOriginalCookerChildWidget(){} +protected: + TSharedPtr mContext; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherCookMaps.cpp b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherCookMaps.cpp new file mode 100644 index 0000000..2e37460 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherCookMaps.cpp @@ -0,0 +1,156 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +// #include "HotPatcherPrivatePCH.h" +#include "SHotPatcherCookMaps.h" +#include "SProjectCookMapListRow.h" +#include "Widgets/Input/SHyperlink.h" +#include "Widgets/Layout/SSeparator.h" +#include "FlibPatchParserHelper.h" +#include "Kismet/KismetSystemLibrary.h" + +#define LOCTEXT_NAMESPACE "SHotPatcherCookMaps" + +void SHotPatcherCookMaps::Construct(const FArguments& InArgs, TSharedPtr InContext) +{ + SetContext(InContext); + + ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .FillHeight(1.0) + .Padding(0.0f, 2.0f, 0.0f, 0.0f) + [ + // map list + SAssignNew(MapListView, SListView >) + .HeaderRow + ( + SNew(SHeaderRow) + .Visibility(EVisibility::Collapsed) + + + SHeaderRow::Column("MapName") + .DefaultLabel(LOCTEXT("MapListMapNameColumnHeader", "Map")) + .FillWidth(1.0f) + ) + .ItemHeight(16.0f) + .ListItemsSource(&MapList) + .OnGenerateRow(this, &SHotPatcherCookMaps::HandleMapListViewGenerateRow) + .SelectionMode(ESelectionMode::None) + ] + + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0f, 6.0f, 0.0f, 4.0f) + [ + SNew(SSeparator) + .Orientation(Orient_Horizontal) + ] + + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .HAlign(HAlign_Right) + [ + SNew(STextBlock) + .Text(LOCTEXT("SelectLabel", "Select:")) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(8.0f, 0.0f) + [ + // all Maps hyper-link + SNew(SHyperlink) + .OnNavigate(this, &SHotPatcherCookMaps::HandleAllMapHyperlinkNavigate, true) + .Text(LOCTEXT("AllMapHyperlinkLabel", "All")) + .ToolTipText(LOCTEXT("AllMapButtonTooltip", "Select all available Maps.")) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + [ + // no Maps hyper-link + SNew(SHyperlink) + .OnNavigate(this, &SHotPatcherCookMaps::HandleAllMapHyperlinkNavigate, false) + .Text(LOCTEXT("NoMapsHyperlinkLabel", "None")) + .ToolTipText(LOCTEXT("NoMapsHyperlinkTooltip", "Deselect all Maps.")) + ] + ] + + ]; + + RefreshMapList(); +} + +TSharedPtr SHotPatcherCookMaps::SerializeAsJson() const +{ + TSharedPtr JsonObject = MakeShareable(new FJsonObject); + + TArray SelectedPlatformList = GetCookerContextPtr()->GetAllSelectedCookMap(); + + TArray> SelectedMapsJsonList; + for (const auto& Platform : SelectedPlatformList) + { + SelectedMapsJsonList.Add(MakeShareable(new FJsonValueString(Platform))); + } + JsonObject->SetArrayField(TEXT("CookMaps"), SelectedMapsJsonList); + JsonObject->SetBoolField(TEXT("bCookAllMap"), IsCookAllMap()); + return JsonObject; +} + +void SHotPatcherCookMaps::DeSerializeFromJsonObj(TSharedPtrconst & InJsonObject) +{ + TArray> SelectedMapsJsonList = InJsonObject->GetArrayField(TEXT("CookMaps")); + bool IsCookAllMap = InJsonObject->GetBoolField(TEXT("bCookAllMap")); + + if (!IsCookAllMap) + { + TArray> SelectedMaps; + for (const auto& PlatformJson : SelectedMapsJsonList) + { + FString Map = PlatformJson->AsString(); + SelectedMaps.Add(MakeShareable(new FString(Map))); + GetCookerContextPtr()->AddSelectedCookMap(Map); + } + } + else + { + HandleAllMapHyperlinkNavigate(IsCookAllMap); + } +} + +FString SHotPatcherCookMaps::GetSerializeName()const +{ + return TEXT("Maps"); +} + +void SHotPatcherCookMaps::Reset() +{ + GetCookerContextPtr()->ClearAllMap(); +} + + +void SHotPatcherCookMaps::RefreshMapList() +{ + MapList.Reset(); + + TArray AvailableMaps = UFlibPatchParserHelper::GetAvailableMaps(UKismetSystemLibrary::GetProjectDirectory(), ENABLE_COOK_ENGINE_MAP, ENABLE_COOK_PLUGIN_MAP, true); + for (int32 AvailableMapIndex = 0; AvailableMapIndex < AvailableMaps.Num(); ++AvailableMapIndex) + { + FString& Map = AvailableMaps[AvailableMapIndex]; + MapList.Add(MakeShareable(new FString(Map))); + } + + MapListView->RequestListRefresh(); +} + +TSharedRef SHotPatcherCookMaps::HandleMapListViewGenerateRow(TSharedPtr InItem, const TSharedRef& OwnerTable) +{ + return SNew(SProjectCookMapListRow, mContext) + .MapName(InItem) + .OwnerTableView(OwnerTable); +} + +#undef LOCTEXT_NAMESPACE diff --git a/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherCookMaps.h b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherCookMaps.h new file mode 100644 index 0000000..2ef49d7 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherCookMaps.h @@ -0,0 +1,72 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "Model/FOriginalCookerContext.h" +#include "Templates/SharedPointer.h" +#include "FlibPatchParserHelper.h" +#include "Kismet/KismetSystemLibrary.h" + +// project header +#include "IOriginalCookerChildWidget.h" + +/** + * Implements the cooked Maps panel. + */ +class SHotPatcherCookMaps + : public SCompoundWidget,public IOriginalCookerChildWidget +{ +public: + + SLATE_BEGIN_ARGS(SHotPatcherCookMaps) { } + SLATE_END_ARGS() + +public: + + /** + * Constructs the widget. + * + * @param InArgs The Slate argument list. + */ + void Construct( const FArguments& InArgs,TSharedPtr InContext); + +public: + virtual TSharedPtr SerializeAsJson()const override; + virtual void DeSerializeFromJsonObj(TSharedPtrconst & InJsonObject)override; + virtual FString GetSerializeName()const override; + virtual void Reset() override; + bool IsCookAllMap()const { return MapList.Num() == GetCookerContextPtr()->GetAllSelectedCookMap().Num(); } +protected: + + + // Callback for clicking the 'Select All Maps' button. + void HandleAllMapHyperlinkNavigate(bool AllMap) + { + if (mContext.IsValid()) + { + if (AllMap) + { + TArray Maps = UFlibPatchParserHelper::GetAvailableMaps(UKismetSystemLibrary::GetProjectDirectory(),false,false,true); + + for (int32 MapIndex = 0; MapIndex < Maps.Num(); ++MapIndex) + { + GetCookerContextPtr()->AddSelectedCookMap(Maps[MapIndex]); + } + } + else { + GetCookerContextPtr()->ClearAllMap(); + } + + } + } + + TSharedRef HandleMapListViewGenerateRow(TSharedPtr InItem, const TSharedRef& OwnerTable); + void RefreshMapList(); +private: + + /** Holds the map list. */ + TArray > MapList; + /** Holds the map list view. */ + TSharedPtr > > MapListView; + +}; + diff --git a/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherCookSetting.cpp b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherCookSetting.cpp new file mode 100644 index 0000000..c9eebc6 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherCookSetting.cpp @@ -0,0 +1,158 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +// #include "HotPatcherPrivatePCH.h" +#include "SHotPatcherCookSetting.h" +#include "SProjectCookSettingsListRow.h" +#include "Widgets/Input/SHyperlink.h" +#include "Widgets/Layout/SSeparator.h" +#include "Kismet/KismetTextLibrary.h" + +#include "FlibHotPatcherCoreHelper.h" + +#define LOCTEXT_NAMESPACE "SHotPatcherCookSetting" + +void SHotPatcherCookSetting::Construct(const FArguments& InArgs, TSharedPtr InContext) +{ + SetContext(InContext); + + ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .FillHeight(1.0) + .Padding(0.0f, 2.0f, 0.0f, 0.0f) + [ + // map list + SAssignNew(SettingListView, SListView >) + .HeaderRow + ( + SNew(SHeaderRow) + .Visibility(EVisibility::Collapsed) + + + SHeaderRow::Column("SettingName") + .DefaultLabel(LOCTEXT("SettingListSettingNameColumnHeader", "Setting")) + .FillWidth(1.0f) + ) + .ItemHeight(16.0f) + .ListItemsSource(&SettingList) + .OnGenerateRow(this, &SHotPatcherCookSetting::HandleCookSettingListViewGenerateRow) + .SelectionMode(ESelectionMode::None) + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0f,2.0f,0.0f,0.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(LOCTEXT("ProjectCookSettingExParama", "Other Options:")) + ] + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0f, 2.0f, 0.0f, 0.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(0.0f, 2.0f, 0.0f, 0.0f) + .FillWidth(1.0f) + [ + SAssignNew(ExternSettingTextBox, SEditableTextBox) + ] + ] + ]; + if(GetCookerContextPtr()) + { + GetCookerContextPtr()->OnRequestExSettings.BindRaw(this, &SHotPatcherCookSetting::HandleRequestExSettings); + } + + RefreshSettingsList(); +} + +TSharedPtr SHotPatcherCookSetting::SerializeAsJson() const +{ + TSharedPtr JsonObject = MakeShareable(new FJsonObject); + + TArray SelectedCookSettingList = GetCookerContextPtr()->GetAllSelectedSettings(); + + TArray> CookSettingJsonList; + for (const auto& Platform : SelectedCookSettingList) + { + CookSettingJsonList.Add(MakeShareable(new FJsonValueString(Platform))); + } + + JsonObject->SetArrayField(TEXT("CookSettings"), CookSettingJsonList); + FString FinalOptions = ExternSettingTextBox->GetText().ToString(); + + for (const auto& DefaultCookParam : GetDefaultCookParams()) + { + if (!FinalOptions.Contains(DefaultCookParam)) + { + FinalOptions.Append(TEXT(" ") + DefaultCookParam); + } + } + JsonObject->SetStringField(TEXT("Options"), FinalOptions); + return JsonObject; +} + +void SHotPatcherCookSetting::DeSerializeFromJsonObj(TSharedPtrconst & InJsonObject) +{ + TArray> CookSettingJsonList = InJsonObject->GetArrayField(TEXT("CookSettings")); + + TArray> SelectedCookSettingList; + for (const auto& SettingItem : CookSettingJsonList) + { + FString Setting = SettingItem->AsString(); + SelectedCookSettingList.Add(MakeShareable(new FString(Setting))); + GetCookerContextPtr()->AddSelectedSetting(Setting); + } + ExternSettingTextBox->SetText(UKismetTextLibrary::Conv_StringToText(InJsonObject->GetStringField("Options"))); +} + + +FString SHotPatcherCookSetting::GetSerializeName()const +{ + return TEXT("Settings"); +} + +void SHotPatcherCookSetting::Reset() +{ + GetCookerContextPtr()->ClearAllSettings(); + ExternSettingTextBox->SetText(UKismetTextLibrary::Conv_StringToText(TEXT(""))); +} + + +TSharedRef SHotPatcherCookSetting::HandleCookSettingListViewGenerateRow(TSharedPtr InItem, const TSharedRef& OwnerTable) +{ + return SNew(SProjectCookSettingsListRow, mContext) + .SettingName(InItem) + .OwnerTableView(OwnerTable); +} + +void SHotPatcherCookSetting::RefreshSettingsList() +{ + SettingList.Reset(); + + + TArray AllSettings = UFlibHotPatcherCoreHelper::GetAllCookOption(); + for (int32 OptionIndex = 0; OptionIndex < AllSettings.Num(); ++OptionIndex) + { + FString& Option = AllSettings[OptionIndex]; + SettingList.Add(MakeShareable(new FString(Option))); + } + + SettingListView->RequestListRefresh(); +} + +void SHotPatcherCookSetting::HandleRequestExSettings(TArray& OutExSettings) +{ + OutExSettings.Reset(); + + FString ExOptins = ExternSettingTextBox->GetText().ToString(); + OutExSettings.Add(ExOptins); +} + +#undef LOCTEXT_NAMESPACE diff --git a/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherCookSetting.h b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherCookSetting.h new file mode 100644 index 0000000..fce3921 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherCookSetting.h @@ -0,0 +1,60 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "Interfaces/ITargetPlatformManagerModule.h" +#include "Interfaces/ITargetPlatform.h" +#include "Model/FOriginalCookerContext.h" +#include "Templates/SharedPointer.h" +#include "Widgets/Input/SEditableTextBox.h" +// project header +#include "IOriginalCookerChildWidget.h" + +/** + * Implements the cooked platforms panel. + */ +class SHotPatcherCookSetting + : public SCompoundWidget,public IOriginalCookerChildWidget +{ +public: + + SLATE_BEGIN_ARGS(SHotPatcherCookSetting) { } + SLATE_END_ARGS() + +public: + + /** + * Constructs the widget. + * + * @param InArgs The Slate argument list. + */ + void Construct( const FArguments& InArgs,TSharedPtr InContext); +public: + virtual TSharedPtr SerializeAsJson()const override; + virtual void DeSerializeFromJsonObj(TSharedPtrconst & InJsonObject)override; + virtual FString GetSerializeName()const override; + virtual void Reset() override; + +protected: + TSharedRef HandleCookSettingListViewGenerateRow(TSharedPtr InItem, const TSharedRef& OwnerTable); + void RefreshSettingsList(); + + void HandleRequestExSettings(TArray& OutExSettings); + // FReply ConfirmExCookSetting(); + + + TArray GetDefaultCookParams() const + { + return TArray{"-NoLogTimes", "-UTF8Output"}; + } + +private: + /** Holds the map list. */ + TArray > SettingList; + + /** Holds the map list view. */ + TSharedPtr > > SettingListView; + + // Extern Cook Setting Param + TSharedPtr ExternSettingTextBox; +}; + diff --git a/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherCookSpecifyCookFilter.cpp b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherCookSpecifyCookFilter.cpp new file mode 100644 index 0000000..15125d6 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherCookSpecifyCookFilter.cpp @@ -0,0 +1,137 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "SHotPatcherCookSpecifyCookFilter.h" +#include "FlibAssetManageHelper.h" +#include "SProjectCookMapListRow.h" +#include "Widgets/Input/SHyperlink.h" +#include "Widgets/Layout/SSeparator.h" +#include "FlibPatchParserHelper.h" +#include "Kismet/KismetSystemLibrary.h" +#include "Misc/EngineVersionComparison.h" + +#if !UE_VERSION_OLDER_THAN(5,1,0) + typedef FAppStyle FEditorStyle; +#endif +#define LOCTEXT_NAMESPACE "SHotPatcherCookSpecifyCookFilter" + +void SHotPatcherCookSpecifyCookFilter::Construct(const FArguments& InArgs, TSharedPtr InContext) +{ + SetContext(InContext); + + GetCookerContextPtr()->OnRequestSpecifyCookFilter.BindRaw(this, &SHotPatcherCookSpecifyCookFilter::HandleRequestSpecifyCookFilter); + CreateFilterListView(); + + ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .VAlign(VAlign_Center) + [ + SettingsView->AsShared() + ] + ] + ]; + SpecifyCookFilterSetting = USpecifyCookFilterSetting::Get(); + SettingsView->SetObject(SpecifyCookFilterSetting); +} + + + +TSharedPtr SHotPatcherCookSpecifyCookFilter::SerializeAsJson() const +{ + TSharedPtr JsonObject = MakeShareable(new FJsonObject); + TArray> CookFiltersJsonValueList; + + for (const auto& Filter : GetSpecifyCookFilterSetting()->GetAlwayCookFilters()) + { + FString FilterPath = Filter.Path; + CookFiltersJsonValueList.Add(MakeShareable(new FJsonValueString(FilterPath))); + } + JsonObject->SetArrayField(TEXT("CookFilter"), CookFiltersJsonValueList); + return JsonObject; +} + +void SHotPatcherCookSpecifyCookFilter::DeSerializeFromJsonObj(TSharedPtrconst & InJsonObject) +{ + TArray> CookFiltersJsonValueList = InJsonObject->GetArrayField(TEXT("CookFilter")); + + for (const auto& FilterJsonValue : CookFiltersJsonValueList) + { + FDirectoryPath FilterDirPath; + FilterDirPath.Path = FilterJsonValue->AsString(); + GetSpecifyCookFilterSetting()->GetAlwayCookFilters().Add(FilterDirPath); + } + +} + +FString SHotPatcherCookSpecifyCookFilter::GetSerializeName()const +{ + return TEXT("Filters"); +} + + +void SHotPatcherCookSpecifyCookFilter::Reset() +{ + GetSpecifyCookFilterSetting()->GetAlwayCookFilters().Reset(); +} + +USpecifyCookFilterSetting* SHotPatcherCookSpecifyCookFilter::GetSpecifyCookFilterSetting() const +{ + return SpecifyCookFilterSetting; +} + + +TArray SHotPatcherCookSpecifyCookFilter::GetAlwayCookDirectory() const +{ + if (GetSpecifyCookFilterSetting()) + { + return GetSpecifyCookFilterSetting()->GetAlwayCookFilters(); + } + else + { + return TArray{}; + } +} + + +TArray SHotPatcherCookSpecifyCookFilter::GetAlwayCookAbsDirectory() +{ + TArray AlwayCookDirAbsPathList; + for (const auto& AlwayCookRelativeDir : GetAlwayCookDirectory()) + { + FString AbsPath; + if (UFlibAssetManageHelper::ConvRelativeDirToAbsDir(AlwayCookRelativeDir.Path, AbsPath)) + { + AlwayCookDirAbsPathList.AddUnique(AbsPath); + } + } + return AlwayCookDirAbsPathList; +} + +void SHotPatcherCookSpecifyCookFilter::HandleRequestSpecifyCookFilter(TArray& OutCookDir) +{ + OutCookDir = GetAlwayCookDirectory(); +} + +void SHotPatcherCookSpecifyCookFilter::CreateFilterListView() +{ + // Create a property view + FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked("PropertyEditor"); + + FDetailsViewArgs DetailsViewArgs; + DetailsViewArgs.bUpdatesFromSelection = true; + DetailsViewArgs.bLockable = true; + DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::ComponentsAndActorsUseNameArea; + DetailsViewArgs.bCustomNameAreaLocation = false; + DetailsViewArgs.bCustomFilterAreaLocation = true; + DetailsViewArgs.DefaultsOnlyVisibility = EEditDefaultsOnlyNodeVisibility::Hide; + + SettingsView = EditModule.CreateDetailView(DetailsViewArgs); +} + +#undef LOCTEXT_NAMESPACE diff --git a/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherCookSpecifyCookFilter.h b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherCookSpecifyCookFilter.h new file mode 100644 index 0000000..34e10b8 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherCookSpecifyCookFilter.h @@ -0,0 +1,56 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "Model/FOriginalCookerContext.h" +#include "Templates/SharedPointer.h" +#include "FlibPatchParserHelper.h" +#include "SpecifyCookFilterSetting.h" + +// engine header +#include "IDetailsView.h" +#include "Templates/SharedPointer.h" +#include "PropertyEditorModule.h" +#include "Kismet/KismetSystemLibrary.h" + +// project header +#include "IOriginalCookerChildWidget.h" + +/** + * Implements the cooked Maps panel. + */ +class SHotPatcherCookSpecifyCookFilter + : public SCompoundWidget,public IOriginalCookerChildWidget +{ +public: + + SLATE_BEGIN_ARGS(SHotPatcherCookSpecifyCookFilter) { } + SLATE_END_ARGS() + +public: + + /** + * Constructs the widget. + * + * @param InArgs The Slate argument list. + */ + void Construct( const FArguments& InArgs,TSharedPtr InContext); +public: + virtual TSharedPtr SerializeAsJson()const override; + virtual void DeSerializeFromJsonObj(TSharedPtrconst & InJsonObject)override; + virtual FString GetSerializeName()const override; + virtual void Reset() override; + +public: + USpecifyCookFilterSetting* GetSpecifyCookFilterSetting()const; + TArray GetAlwayCookDirectory()const; + TArray GetAlwayCookAbsDirectory(); + void HandleRequestSpecifyCookFilter(TArray& OutCookDir); +protected: + void CreateFilterListView(); + +private: + /** Settings view ui element ptr */ + TSharedPtr SettingsView; + USpecifyCookFilterSetting* SpecifyCookFilterSetting; +}; + diff --git a/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherCookedPlatforms.cpp b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherCookedPlatforms.cpp new file mode 100644 index 0000000..3a82584 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherCookedPlatforms.cpp @@ -0,0 +1,130 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +// #include "HotPatcherPrivatePCH.h" +#include "SHotPatcherCookedPlatforms.h" +#include "SHotPatcherPlatformListRow.h" +#include "Widgets/Input/SHyperlink.h" +#include "Widgets/Layout/SSeparator.h" + +#define LOCTEXT_NAMESPACE "SHotPatcherCookedPlatforms" + +void SHotPatcherCookedPlatforms::Construct(const FArguments& InArgs, TSharedPtr InContext) +{ + mContext = InContext; + MakePlatformMenu(); + + ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .MaxHeight(256.0f) + [ + // platform menu + SAssignNew(PlatformListView, SListView >) + .HeaderRow( + SNew(SHeaderRow) + .Visibility(EVisibility::Collapsed) + + + SHeaderRow::Column("PlatformName") + .DefaultLabel(LOCTEXT("PlatformListPlatformNameColumnHeader", "Platform")) + .FillWidth(1.0f) + ) + .ItemHeight(16.0f) + .ListItemsSource(&PlatformList) + .OnGenerateRow(this, &SHotPatcherCookedPlatforms::HandlePlatformListViewGenerateRow) + .SelectionMode(ESelectionMode::None) + ] + + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0f, 6.0f, 0.0f, 4.0f) + [ + SNew(SSeparator) + .Orientation(Orient_Horizontal) + ] + + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .HAlign(HAlign_Right) + [ + SNew(STextBlock) + .Text(LOCTEXT("SelectLabel", "Select:")) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(8.0f, 0.0f) + [ + // all platforms hyper-link + SNew(SHyperlink) + .OnNavigate(this, &SHotPatcherCookedPlatforms::HandleAllPlatformsHyperlinkNavigate, true) + .Text(LOCTEXT("AllPlatformsHyperlinkLabel", "All")) + .ToolTipText(LOCTEXT("AllPlatformsButtonTooltip", "Select all available platforms.")) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + [ + // no platforms hyper-link + SNew(SHyperlink) + .OnNavigate(this, &SHotPatcherCookedPlatforms::HandleAllPlatformsHyperlinkNavigate, false) + .Text(LOCTEXT("NoPlatformsHyperlinkLabel", "None")) + .ToolTipText(LOCTEXT("NoPlatformsHyperlinkTooltip", "Deselect all platforms.")) + ] + ] + ]; +} + +TSharedPtr SHotPatcherCookedPlatforms::SerializeAsJson() const +{ + TSharedPtr JsonObject = MakeShareable(new FJsonObject); + + TArray SelectedPlatformList = GetCookerContextPtr()->GetAllSelectedPlatform(); + + TArray> PlatformJsonList; + for (const auto& Platform : SelectedPlatformList) + { + PlatformJsonList.Add(MakeShareable(new FJsonValueString(Platform))); + } + JsonObject->SetArrayField(TEXT("CookPlatforms"), PlatformJsonList); + return JsonObject; +} + + +void SHotPatcherCookedPlatforms::DeSerializeFromJsonObj(TSharedPtrconst & InJsonObject) +{ + TArray> PlatformJsonList = InJsonObject->GetArrayField(TEXT("CookPlatforms")); + + TArray> SelectedPlatform; + for (const auto& PlatformJson : PlatformJsonList) + { + FString Platform = PlatformJson->AsString(); + SelectedPlatform.Add(MakeShareable(new FString(Platform))); + GetCookerContextPtr()->AddSelectedCookPlatform(Platform); + } +} + + +FString SHotPatcherCookedPlatforms::GetSerializeName()const +{ + return TEXT("Platforms"); +} + +void SHotPatcherCookedPlatforms::Reset() +{ + GetCookerContextPtr()->ClearAllPlatform(); +} +TSharedRef SHotPatcherCookedPlatforms::HandlePlatformListViewGenerateRow(TSharedPtr InItem, const TSharedRef& OwnerTable) +{ + return SNew(SHotPatcherPlatformListRow,mContext) + .PlatformName(InItem) + .OwnerTableView(OwnerTable); +} + +#undef LOCTEXT_NAMESPACE diff --git a/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherCookedPlatforms.h b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherCookedPlatforms.h new file mode 100644 index 0000000..f379695 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherCookedPlatforms.h @@ -0,0 +1,96 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "Interfaces/ITargetPlatformManagerModule.h" +#include "Interfaces/ITargetPlatform.h" +#include "Model/FOriginalCookerContext.h" +#include "SHotPatcherWidgetBase.h" +#include "Templates/SharedPointer.h" +#include "Dom/JsonObject.h" + +// project header +#include "IOriginalCookerChildWidget.h" + +/** + * Implements the cooked platforms panel. + */ +class SHotPatcherCookedPlatforms + : public SCompoundWidget,public IOriginalCookerChildWidget +{ +public: + + SLATE_BEGIN_ARGS(SHotPatcherCookedPlatforms) { } + SLATE_END_ARGS() + +public: + + /** + * Constructs the widget. + * + * @param InArgs The Slate argument list. + */ + void Construct( const FArguments& InArgs,TSharedPtr InContext); + +public: + virtual TSharedPtr SerializeAsJson()const override; + virtual void DeSerializeFromJsonObj(TSharedPtrconst & InJsonObject)override; + virtual FString GetSerializeName()const override; + virtual void Reset() override; +protected: + + /** + * Builds the platform menu. + * + * @return Platform menu widget. + */ + void MakePlatformMenu( ) + { + TArray Platforms = GetTargetPlatformManager()->GetTargetPlatforms(); + + if (Platforms.Num() > 0) + { + PlatformList.Reset(); + for (int32 PlatformIndex = 0; PlatformIndex < Platforms.Num(); ++PlatformIndex) + { + FString PlatformName = Platforms[PlatformIndex]->PlatformName(); + + PlatformList.Add(MakeShareable(new FString(PlatformName))); + } + } + } + +private: + + // Callback for clicking the 'Select All Platforms' button. + void HandleAllPlatformsHyperlinkNavigate( bool AllPlatforms ) + { + + if (mContext.IsValid()) + { + if (AllPlatforms) + { + TArray Platforms = GetTargetPlatformManager()->GetTargetPlatforms(); + + for (int32 PlatformIndex = 0; PlatformIndex < Platforms.Num(); ++PlatformIndex) + { + GetCookerContextPtr()->AddSelectedCookPlatform(Platforms[PlatformIndex]->PlatformName()); + } + } + else { + GetCookerContextPtr()->ClearAllPlatform(); + } + + } + } + + // Handles generating a row widget in the map list view. + TSharedRef HandlePlatformListViewGenerateRow( TSharedPtr InItem, const TSharedRef& OwnerTable ); + +private: + // Holds the platform list. + TArray > PlatformList; + // Holds the platform list view. + TSharedPtr > > PlatformListView; + +}; + diff --git a/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherPlatformListRow.h b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherPlatformListRow.h new file mode 100644 index 0000000..e81b515 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SHotPatcherPlatformListRow.h @@ -0,0 +1,114 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Misc/Attribute.h" +#include "Widgets/SNullWidget.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SWidget.h" +#include "Layout/Margin.h" +#include "Styling/SlateTypes.h" +#include "Widgets/Views/STableRow.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/Views/SListView.h" +#include "Widgets/Input/SCheckBox.h" +#include "Model/FOriginalCookerContext.h" +#include "Templates/SharedPointer.h" + +#define LOCTEXT_NAMESPACE "SHotPatcherPlatformListRow" + +/** + * Implements a row widget for map list. + */ +class SHotPatcherPlatformListRow + : public SMultiColumnTableRow >,IOriginalCookerChildWidget +{ +public: + + SLATE_BEGIN_ARGS(SHotPatcherPlatformListRow) { } + SLATE_ATTRIBUTE(FString, HighlightString) + SLATE_ARGUMENT(TSharedPtr, OwnerTableView) + SLATE_ARGUMENT(TSharedPtr, PlatformName) + SLATE_END_ARGS() + +public: + + /** + * Constructs the widget. + * + * @param InArgs The construction arguments. + * @param InProfileManager The profile manager to use. + */ + void Construct( const FArguments& InArgs, TSharedPtr InContext) + { + HighlightString = InArgs._HighlightString; + PlatformName = InArgs._PlatformName; + + SetContext(InContext); + SMultiColumnTableRow >::Construct(FSuperRowType::FArguments(), InArgs._OwnerTableView.ToSharedRef()); + } + +public: + + /** + * Generates the widget for the specified column. + * + * @param ColumnName The name of the column to generate the widget for. + * @return The widget. + */ + virtual TSharedRef GenerateWidgetForColumn( const FName& ColumnName ) override + { + if (ColumnName == "PlatformName") + { + return SNew(SCheckBox) + .IsChecked(this, &SHotPatcherPlatformListRow::HandleCheckBoxIsChecked) + .OnCheckStateChanged(this, &SHotPatcherPlatformListRow::HandleCheckBoxCheckStateChanged) + .Padding(FMargin(6.0, 2.0)) + [ + SNew(STextBlock) + .Text(FText::FromString(*PlatformName)) + ]; + } + + return SNullWidget::NullWidget; + } + +private: + + // Callback for changing the checked state of the check box. + void HandleCheckBoxCheckStateChanged( ECheckBoxState NewState ) + { + if (NewState == ECheckBoxState::Checked) + { + GetCookerContextPtr()->AddSelectedCookPlatform(*PlatformName); + } + else { + GetCookerContextPtr()->RemoveSelectedCookPlatform(*PlatformName); + } + } + + // Callback for determining the checked state of the check box. + ECheckBoxState HandleCheckBoxIsChecked( ) const + { + if (mContext.IsValid()) + { + if (GetCookerContextPtr()->GetAllSelectedPlatform().Contains(*PlatformName)) + { + return ECheckBoxState::Checked; + } + } + return ECheckBoxState::Unchecked; + } + +private: + + // Holds the highlight string for the log message. + TAttribute HighlightString; + + // Holds the platform's name. + TSharedPtr PlatformName; +}; + + +#undef LOCTEXT_NAMESPACE diff --git a/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SOriginalCookWidget.cpp b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SOriginalCookWidget.cpp new file mode 100644 index 0000000..4b6c95a --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SOriginalCookWidget.cpp @@ -0,0 +1,351 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "SOriginalCookWidget.h" +#include "SHotPatcherCookedPlatforms.h" +#include "SHotPatcherCookSetting.h" +#include "SHotPatcherCookMaps.h" +#include "SOriginalCookWidget.h" +#include "FlibPatchParserHelper.h" +#include "ThreadUtils/FProcWorkerThread.hpp" +#include "FlibHotPatcherCoreHelper.h" + +// Engine Header +#include "FlibHotPatcherEditorHelper.h" +#include "Serialization/JsonWriter.h" +#include "Serialization/JsonReader.h" + +#include "Widgets/SBoxPanel.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/Layout/SExpandableArea.h" +#include "Widgets/Notifications/SNotificationList.h" +#include "Async/Async.h" + + +#define LOCTEXT_NAMESPACE "SProjectCookPage" + +DEFINE_LOG_CATEGORY(LogCookPage); + +/* SProjectCookPage interface + *****************************************************************************/ + +void SOriginalCookWidget::Construct(const FArguments& InArgs, TSharedPtr InContext) +{ + OriginalCookerContext = MakeShareable(new FOriginalCookerContext); + SetContext(OriginalCookerContext); + + MissionNotifyProay = NewObject(); + MissionNotifyProay->AddToRoot(); + MissionNotifyProay->SetMissionName(TEXT("Cook")); + MissionNotifyProay->SetMissionNotifyText( + LOCTEXT("CookNotificationInProgress", "Cook in progress"), + LOCTEXT("RunningCookNotificationCancelButton", "Cancel"), + LOCTEXT("CookSuccessedNotification", "Cook Finished!"), + LOCTEXT("CookFaildNotification", "Cook Faild!") + ); + + ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0, 8.0, 0.0, 0.0) + [ + SNew(SExpandableArea) + .AreaTitle(LOCTEXT("CookPlatforms", "Platforms")) + .InitiallyCollapsed(true) + .Padding(8.0) + .BodyContent() + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + [ + SAssignNew(Platforms, SHotPatcherCookedPlatforms, OriginalCookerContext) + ] + ] + + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0, 8.0, 0.0, 0.0) + [ + SNew(SExpandableArea) + .AreaTitle(LOCTEXT("CookMaps", "Map(s)")) + .InitiallyCollapsed(true) + .Padding(8.0) + .BodyContent() + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + [ + SAssignNew(CookMaps, SHotPatcherCookMaps, OriginalCookerContext) + ] + ] + + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0, 8.0, 0.0, 0.0) + [ + SNew(SExpandableArea) + .AreaTitle(LOCTEXT("CookFilter", "Filter(s)")) + .InitiallyCollapsed(true) + .Padding(8.0) + .BodyContent() + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + [ + SAssignNew(CookFilters, SHotPatcherCookSpecifyCookFilter, OriginalCookerContext) + ] + ] + + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0, 8.0, 0.0, 0.0) + [ + SNew(SExpandableArea) + .AreaTitle(LOCTEXT("CookSetting", "Settings")) + .InitiallyCollapsed(true) + .Padding(8.0) + .BodyContent() + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + [ + SAssignNew(CookSettings, SHotPatcherCookSetting, OriginalCookerContext) + ] + ] + + ] + + 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("RunCook", "Cook Content")) + .OnClicked(this, &SOriginalCookWidget::RunCook) + .IsEnabled(this, &SOriginalCookWidget::CanExecuteCook) + ] + ]; +} +#include "DesktopPlatformModule.h" + +namespace FPlatformUtils +{ + TArray OpenFileDialog() + { + IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); + TArray SelectedFiles; + + if (DesktopPlatform) + { + const bool bOpened = DesktopPlatform->OpenFileDialog( + nullptr, + LOCTEXT("OpenCookConfigDialog", "Open .json").ToString(), + FString(TEXT("")), + TEXT(""), + TEXT("CookConfig json (*.json)|*.json"), + EFileDialogFlags::None, + SelectedFiles + ); + } + return SelectedFiles; + } + + TArray SaveFileDialog() + { + IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); + + TArray SaveFilenames; + if (DesktopPlatform) + { + + const bool bOpened = DesktopPlatform->SaveFileDialog( + nullptr, + LOCTEXT("SvedCookConfig", "Save .json").ToString(), + FString(TEXT("")), + TEXT(""), + TEXT("CookConfig json (*.json)|*.json"), + EFileDialogFlags::None, + SaveFilenames + ); + } + return SaveFilenames; + } + + TSharedPtr DeserializeAsJsonObject(const FString& InContent) + { + TSharedRef> JsonReader = TJsonReaderFactory::Create(InContent); + TSharedPtr JsonObject; + FJsonSerializer::Deserialize(JsonReader, JsonObject); + return JsonObject; + } +} + +#include "Misc/FileHelper.h" + +FReply SOriginalCookWidget::DoImportConfig() +{ + ImportConfig(); + return FReply::Handled(); +} + +FReply SOriginalCookWidget::DoExportConfig() const +{ + ExportConfig(); + + + return FReply::Handled(); +} + +FReply SOriginalCookWidget::DoResetConfig() +{ + ResetConfig(); + return FReply::Handled(); +} +bool SOriginalCookWidget::CanExecuteCook()const +{ + bool bCanCook = !!GetCookerContextPtr()->GetAllSelectedPlatform().Num() && + ( + !!GetCookerContextPtr()->GetAllSelectedCookMap().Num() || + !!GetCookerContextPtr()->GetAlwayCookFilters().Num() || + GetCookerContextPtr()->GetAllSelectedSettings().Contains(TEXT("CookAll")) + ); + + return !InCooking && bCanCook; +} + +FReply SOriginalCookWidget::RunCook()const +{ + FCookerConfig CookConfig = GetCookerContextPtr()->GetCookConfig(); + + if (FPaths::FileExists(CookConfig.EngineBin) && FPaths::FileExists(CookConfig.ProjectPath)) + { + FString CookCommand; + UFlibPatchParserHelper::GetCookProcCommandParams(CookConfig, CookCommand); + UE_LOG(LogCookPage, Log, TEXT("The Cook Mission is Staring...")); + UE_LOG(LogCookPage, Log, TEXT("CookCommand:%s %s"),*CookConfig.EngineBin,*CookCommand); + RunCookProc(CookConfig.EngineBin, CookCommand); + } + return FReply::Handled(); +} + +void SOriginalCookWidget::CancelCookMission() +{ + if (mCookProcWorkingThread.IsValid() && mCookProcWorkingThread->GetThreadStatus() == EThreadStatus::Busy) + { + mCookProcWorkingThread->Cancel(); + } + UE_LOG(LogCookPage, Error, TEXT("The Cook Mission is canceled.")); +} +void SOriginalCookWidget::RunCookProc(const FString& InBinPath, const FString& InCommand)const +{ + if (mCookProcWorkingThread.IsValid() && mCookProcWorkingThread->GetThreadStatus()==EThreadStatus::Busy) + { + mCookProcWorkingThread->Cancel(); + } + else + { + mCookProcWorkingThread = MakeShareable(new FProcWorkerThread(TEXT("CookThread"), InBinPath, InCommand)); + mCookProcWorkingThread->ProcOutputMsgDelegate.BindUObject(MissionNotifyProay,&UMissionNotificationProxy::ReceiveOutputMsg); + mCookProcWorkingThread->ProcBeginDelegate.AddUObject(MissionNotifyProay,&UMissionNotificationProxy::SpawnRuningMissionNotification); + mCookProcWorkingThread->ProcSuccessedDelegate.AddUObject(MissionNotifyProay,&UMissionNotificationProxy::SpawnMissionSuccessedNotification); + mCookProcWorkingThread->ProcFaildDelegate.AddUObject(MissionNotifyProay,&UMissionNotificationProxy::SpawnMissionFaildNotification); + MissionNotifyProay->MissionCanceled.AddRaw(const_cast(this),&SOriginalCookWidget::CancelCookMission); + mCookProcWorkingThread->Execute(); + } +} + +TSharedPtr SOriginalCookWidget::SerializeAsJson() const +{ + FCookerConfig CookConfig = GetCookerContextPtr()->GetCookConfig(); + TSharedPtr CookConfigJsonObj; + THotPatcherTemplateHelper::TSerializeStructAsJsonObject(CookConfig,CookConfigJsonObj); + return CookConfigJsonObj; +} + +void SOriginalCookWidget::DeSerializeFromJsonObj(TSharedPtrconst & InJsonObject) +{ + for (const auto& SerializableItem : GetSerializableItems()) + { + // TSharedPtr ItemJsonObject = InJsonObject->GetObjectField(SerializableItem->GetSerializeName()); + SerializableItem->DeSerializeFromJsonObj(InJsonObject); + } +} + +FString SOriginalCookWidget::GetSerializeName()const +{ + return TEXT("CookPage"); +} + +FString SOriginalCookWidget::SerializeAsString()const +{ + FString result; + auto JsonWriter = TJsonWriterFactory<>::Create(&result); + FJsonSerializer::Serialize(SerializeAsJson().ToSharedRef(), JsonWriter); + return result; +} + +void SOriginalCookWidget::Reset() +{ + for (const auto& SerializableItem : GetSerializableItems()) + { + SerializableItem->Reset(); + } +} + +void SOriginalCookWidget::ImportConfig() +{ + SHotPatcherCookerBase::ImportConfig(); + TArray SelectedFiles = FPlatformUtils::OpenFileDialog(); + + if (!!SelectedFiles.Num()) + { + FString LoadFile = SelectedFiles[0]; + if (FPaths::FileExists(LoadFile)) + { + FString ReadContent; + if (FFileHelper::LoadFileToString(ReadContent, *LoadFile)) + { + DeSerializeFromJsonObj(FPlatformUtils::DeserializeAsJsonObject(ReadContent)); + } + } + + } +} + +void SOriginalCookWidget::ExportConfig() const +{ + SHotPatcherCookerBase::ExportConfig(); + TArray SaveFiles = FPlatformUtils::SaveFileDialog(); + if (!!SaveFiles.Num()) + { + FString SaveToFile = SaveFiles[0].EndsWith(TEXT(".json")) ? SaveFiles[0] : SaveFiles[0].Append(TEXT(".json")); + + if (FFileHelper::SaveStringToFile(SerializeAsString(), *SaveToFile)) + { + FText Msg = LOCTEXT("SavedCookerConfig", "Successd to Export the Cooker Config."); + UFlibHotPatcherEditorHelper::CreateSaveFileNotify(Msg, SaveToFile); + } + } +} + +void SOriginalCookWidget::ResetConfig() +{ + for (const auto& SerializableItem : GetSerializableItems()) + { + SerializableItem->Reset(); + } + SHotPatcherCookerBase::ResetConfig(); +} +#undef LOCTEXT_NAMESPACE diff --git a/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SOriginalCookWidget.h b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SOriginalCookWidget.h new file mode 100644 index 0000000..61b4b4b --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SOriginalCookWidget.h @@ -0,0 +1,87 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "Cooker/SHotPatcherCookerBase.h" + +#include "CoreMinimal.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" +#include "Model/FOriginalCookerContext.h" +#include "ThreadUtils/FProcWorkerThread.hpp" + +#include "IOriginalCookerChildWidget.h" +#include "MissionNotificationProxy.h" +#include "SHotPatcherCookedPlatforms.h" +#include "SHotPatcherCookMaps.h" +#include "SHotPatcherCookSetting.h" +#include "SHotPatcherCookSpecifyCookFilter.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogCookPage, Log, All); + +/** + * Implements the profile page for the session launcher wizard. + */ +class SOriginalCookWidget + : public SHotPatcherCookerBase,public IOriginalCookerChildWidget +{ +public: + + SLATE_BEGIN_ARGS(SOriginalCookWidget) { } + SLATE_END_ARGS() + +public: + + /** + * Constructs the widget. + * + * @param InArgs The Slate argument list. + */ + void Construct( const FArguments& InArgs,TSharedPtr InContext); + +public: + virtual TSharedPtr SerializeAsJson()const override; + virtual void DeSerializeFromJsonObj(TSharedPtrconst & InJsonObject)override; + virtual FString GetSerializeName()const override; + virtual void Reset() override; + + virtual void ImportConfig() override; + virtual void ExportConfig()const override; + virtual void ResetConfig() override; +protected: + FReply DoImportConfig(); + FReply DoExportConfig()const; + FReply DoResetConfig(); + + bool CanExecuteCook()const; + void RunCookProc(const FString& InBinPath, const FString& InCommand)const; + FReply RunCook()const; + +protected: + TArray GetDefaultCookParams()const; + void CancelCookMission(); + + TArray GetSerializableItems()const + { + TArray List; + List.Add(Platforms.Get()); + List.Add(CookMaps.Get()); + List.Add(CookFilters.Get()); + List.Add(CookSettings.Get()); + return List; + } + + FString SerializeAsString()const; + +private: + UMissionNotificationProxy* MissionNotifyProay = nullptr; + bool InCooking=false; + /** The pending progress message */ + TWeakPtr PendingProgressPtr; + + mutable TSharedPtr mCookProcWorkingThread; + TSharedPtr Platforms; + TSharedPtr CookMaps; + TSharedPtr CookFilters; + TSharedPtr CookSettings; + TSharedPtr OriginalCookerContext; +}; diff --git a/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SProjectCookMapListRow.h b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SProjectCookMapListRow.h new file mode 100644 index 0000000..e476d27 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SProjectCookMapListRow.h @@ -0,0 +1,119 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Misc/Attribute.h" +#include "Widgets/SNullWidget.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SWidget.h" +#include "Layout/Margin.h" +#include "Styling/SlateTypes.h" +#include "Widgets/Views/STableRow.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Views/SListView.h" + +#include "Model/FOriginalCookerContext.h" + +#define LOCTEXT_NAMESPACE "SProjectCookMapListRow" + +/** + * Implements a row widget for map list. + */ +class SProjectCookMapListRow + : public SMultiColumnTableRow >,IOriginalCookerChildWidget +{ +public: + + SLATE_BEGIN_ARGS(SProjectCookMapListRow) { } + SLATE_ATTRIBUTE(FString, HighlightString) + SLATE_ARGUMENT(TSharedPtr, OwnerTableView) + SLATE_ARGUMENT(TSharedPtr, MapName) + SLATE_END_ARGS() + +public: + + /** + * Constructs the widget. + * + * @param InArgs The construction arguments. + * @param InProfileManager The profile manager to use. + */ + void Construct( const FArguments& InArgs, const TSharedPtr& InModel ) + { + HighlightString = InArgs._HighlightString; + MapName = InArgs._MapName; + SetContext(InModel); + + SMultiColumnTableRow >::Construct(FSuperRowType::FArguments(), InArgs._OwnerTableView.ToSharedRef()); + } + +public: + + /** + * Generates the widget for the specified column. + * + * @param ColumnName The name of the column to generate the widget for. + * @return The widget. + */ + virtual TSharedRef GenerateWidgetForColumn( const FName& ColumnName ) override + { + if (ColumnName == "MapName") + { + return SNew(SCheckBox) + .IsChecked(this, &SProjectCookMapListRow::HandleCheckBoxIsChecked) + .OnCheckStateChanged(this, &SProjectCookMapListRow::HandleCheckBoxCheckStateChanged) + .Padding(FMargin(6.0, 2.0)) + [ + SNew(STextBlock) + .Text(FText::FromString(*MapName)) + ]; + } + + return SNullWidget::NullWidget; + } + +private: + + // Callback for changing the checked state of the check box. + void HandleCheckBoxCheckStateChanged( ECheckBoxState NewState ) + { + + if (mContext.IsValid()) + { + if (NewState == ECheckBoxState::Checked) + { + GetCookerContextPtr()->AddSelectedCookMap(*MapName); + } + else + { + GetCookerContextPtr()->RemoveSelectedCookMap(*MapName); + } + } + } + + // Callback for determining the checked state of the check box. + ECheckBoxState HandleCheckBoxIsChecked( ) const + { + + if (mContext.IsValid() && GetCookerContextPtr()->GetAllSelectedCookMap().Contains(*MapName)) + { + return ECheckBoxState::Checked; + } + + return ECheckBoxState::Unchecked; + } + +private: + + // Holds the highlight string for the log message. + TAttribute HighlightString; + + // Holds the map's name. + TSharedPtr MapName; + +}; + + +#undef LOCTEXT_NAMESPACE diff --git a/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SProjectCookSettingsListRow.h b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SProjectCookSettingsListRow.h new file mode 100644 index 0000000..8b23b39 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SProjectCookSettingsListRow.h @@ -0,0 +1,118 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Misc/Attribute.h" +#include "Widgets/SNullWidget.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SWidget.h" +#include "Layout/Margin.h" +#include "Styling/SlateTypes.h" +#include "Widgets/Views/STableRow.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Views/SListView.h" + +#include "Model/FOriginalCookerContext.h" + +#define LOCTEXT_NAMESPACE "SProjectCookSettingsListRow" + +/** + * Implements a row widget for map list. + */ +class SProjectCookSettingsListRow + : public SMultiColumnTableRow >,public IOriginalCookerChildWidget +{ +public: + + SLATE_BEGIN_ARGS(SProjectCookSettingsListRow) { } + SLATE_ATTRIBUTE(FString, HighlightString) + SLATE_ARGUMENT(TSharedPtr, OwnerTableView) + SLATE_ARGUMENT(TSharedPtr, SettingName) + SLATE_END_ARGS() + +public: + + /** + * Constructs the widget. + * + * @param InArgs The construction arguments. + * @param InProfileManager The profile manager to use. + */ + void Construct( const FArguments& InArgs, const TSharedPtr& InModel ) + { + HighlightString = InArgs._HighlightString; + SettingName = InArgs._SettingName; + SetContext(InModel); + + SMultiColumnTableRow >::Construct(FSuperRowType::FArguments(), InArgs._OwnerTableView.ToSharedRef()); + } + +public: + + /** + * Generates the widget for the specified column. + * + * @param ColumnName The name of the column to generate the widget for. + * @return The widget. + */ + virtual TSharedRef GenerateWidgetForColumn( const FName& ColumnName ) override + { + if (ColumnName == "SettingName") + { + return SNew(SCheckBox) + .IsChecked(this, &SProjectCookSettingsListRow::HandleCheckBoxIsChecked) + .OnCheckStateChanged(this, &SProjectCookSettingsListRow::HandleCheckBoxCheckStateChanged) + .Padding(FMargin(6.0, 2.0)) + [ + SNew(STextBlock) + .Text(FText::FromString(*SettingName)) + ]; + } + + return SNullWidget::NullWidget; + } + +private: + + // Callback for changing the checked state of the check box. + void HandleCheckBoxCheckStateChanged( ECheckBoxState NewState ) + { + + if (mContext.IsValid()) + { + if (NewState == ECheckBoxState::Checked) + { + GetCookerContextPtr()->AddSelectedSetting(*SettingName); + } + else + { + GetCookerContextPtr()->RemoveSelectedSetting(*SettingName); + } + } + } + + // Callback for determining the checked state of the check box. + ECheckBoxState HandleCheckBoxIsChecked( ) const + { + + if (mContext.IsValid() && GetCookerContextPtr()->GetAllSelectedSettings().Contains(*SettingName)) + { + return ECheckBoxState::Checked; + } + + return ECheckBoxState::Unchecked; + } + +private: + + // Holds the highlight string for the log message. + TAttribute HighlightString; + + // Holds the map's name. + TSharedPtr SettingName; +}; + + +#undef LOCTEXT_NAMESPACE diff --git a/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SpecifyCookFilterSetting.h b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SpecifyCookFilterSetting.h new file mode 100644 index 0000000..f12fcc6 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/OriginalCooker/SpecifyCookFilterSetting.h @@ -0,0 +1,46 @@ +#pragma once +// engine header +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "UObject/Object.h" +#include "Engine/EngineTypes.h" +#include "Dom/JsonObject.h" +#include "Serialization/JsonWriter.h" +#include "Serialization/JsonSerializer.h" +#include "SpecifyCookFilterSetting.generated.h" + +/** Singleton wrapper to allow for using the setting structure in SSettingsView */ +UCLASS() +class USpecifyCookFilterSetting : public UObject +{ + GENERATED_BODY() +public: + + USpecifyCookFilterSetting() {} + + FORCEINLINE static USpecifyCookFilterSetting* Get() + { + static bool bInitialized = false; + // This is a singleton, use default object + USpecifyCookFilterSetting* DefaultSettings = GetMutableDefault(); + + if (!bInitialized) + { + bInitialized = true; + } + + return DefaultSettings; + } + + FORCEINLINE TArray& GetAlwayCookFilters(){return AlwayCookFilters;} + FORCEINLINE TArray& GetCookAssets(){return CookAssets;} + +protected: + UPROPERTY(EditAnywhere, BlueprintReadWrite,Category = "Directory",meta = (RelativeToGameContentDir, LongPackageName)) + TArray AlwayCookFilters; + // UPROPERTY(EditAnywhere, BlueprintReadWrite,Category = "Assets") + TArray CookAssets; + //UPROPERTY(EditAnywhere, BlueprintReadWrite,Category = "CookDirectoryFilter",meta = (RelativeToGameContentDir, LongPackageName)) + // TArray NeverCookFilters; + +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherEditor/Private/Cooker/SCookersPage.cpp b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/SCookersPage.cpp new file mode 100644 index 0000000..ce96ef1 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/SCookersPage.cpp @@ -0,0 +1,174 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "SCookersPage.h" +#include "Cooker/OriginalCooker/SOriginalCookWidget.h" +// #include "Cooker/MultiCooker/SHotPatcherMultiCookerPage.h" +// engine header +#include "Framework/Commands/UIAction.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/Layout/SExpandableArea.h" +#include "HotPatcherEditor.h" +#define LOCTEXT_NAMESPACE "SProjectCookerPage" + + +/* SProjectCookerPage interface + *****************************************************************************/ + +void SCookersPage::Construct(const FArguments& InArgs, TSharedPtr InContext) +{ + SetContext(InContext); + FHotPatcherEditorModule& EditorModule = FHotPatcherEditorModule::Get(); + + // create cook modes menu + FMenuBuilder PatchModeMenuBuilder(true, NULL); + { + TMap* Cookers = FHotPatcherActionManager::Get().GetHotPatcherActions().Find(GetPageName()); + if(Cookers) + { + for(const auto& Cooker:*Cookers) + { + if(FHotPatcherActionManager::Get().IsSupportEditorAction(Cooker.Key)) + { + FUIAction Action = FExecuteAction::CreateSP(this, &SCookersPage::HandleHotPatcherMenuEntryClicked, Cooker.Key,Cooker.Value.ActionCallback); + PatchModeMenuBuilder.AddMenuEntry(Cooker.Value.ActionName, Cooker.Value.ActionToolTip, Cooker.Value.Icon, Action); + } + } + } + } + auto Widget = SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .FillWidth(1.0) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(LOCTEXT("WhichProjectToUseText", "How would you like to Cook the content?")) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(8.0, 0.0, 0.0, 0.0) + [ + SNew(SButton) + .Text(LOCTEXT("ImportProjectConfig", "Import Project Config")) + .OnClicked(this,&SHotPatcherPageBase::DoImportProjectConfig) + .Visibility(this,&SCookersPage::HandleImportProjectConfigVisibility) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(8.0, 0.0, 0.0, 0.0) + [ + SNew(SButton) + .Text(LOCTEXT("ImportConfig", "Import")) + .OnClicked(this,&SHotPatcherPageBase::DoImportConfig) + .Visibility(this,&SCookersPage::HandleOperatorConfigVisibility) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(5.0, 0.0, 0.0, 0.0) + [ + SNew(SButton) + .Text(LOCTEXT("ExportConfig", "Export")) + .OnClicked(this,&SHotPatcherPageBase::DoExportConfig) + .Visibility(this, &SCookersPage::HandleOperatorConfigVisibility) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(5.0, 0.0, 0.0, 0.0) + [ + SNew(SButton) + .Text(LOCTEXT("ResetConfig", "Reset")) + .OnClicked(this, &SHotPatcherPageBase::DoResetConfig) + .Visibility(this, &SCookersPage::HandleOperatorConfigVisibility) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(5.0, 0.0, 0.0, 0.0) + [ + // cooking mode menu + SNew(SComboButton) + .ButtonContent() + [ + SNew(STextBlock) + .Text(this, &SCookersPage::HandleCookerModeComboButtonContentText) + ] + .ContentPadding(FMargin(6.0, 2.0)) + .MenuContent() + [ + PatchModeMenuBuilder.MakeWidget() + ] + ] + ]; + + TMap* Cookers = FHotPatcherActionManager::Get().GetHotPatcherActions().Find(GetPageName()); + if(Cookers) + { + for(const auto& Cooker:*Cookers) + { + 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); + } + } + } + ChildSlot + [ + Widget + ]; + + if(FHotPatcherAction* DefaultAction = FHotPatcherActionManager::Get().GetTopActionByCategory(GetPageName())) + { + HandleHotPatcherMenuEntryClicked(UKismetTextLibrary::Conv_TextToString(DefaultAction->ActionName.Get()),nullptr); + } +} + + +EVisibility SCookersPage::HandleOperatorConfigVisibility()const +{ + return EVisibility::Visible; +} + +EVisibility SCookersPage::HandleImportProjectConfigVisibility() const +{ + EVisibility rVisibility = EVisibility::Hidden; + if(GetContext()->GetModeName().IsEqual(TEXT("ByOriginal"))) + { + rVisibility = EVisibility::Visible; + } + return rVisibility; +} + +void SCookersPage::HandleHotPatcherMenuEntryClicked(FString InModeName,TFunction ActionCallback) +{ + if(ActionCallback) + { + ActionCallback(); + } + if (GetContext().IsValid()) + { + GetContext()->SetModeByName(FName(*InModeName)); + } +} + +FText SCookersPage::HandleCookerModeComboButtonContentText() const +{ + if (GetContext().IsValid()) + { + return FText::FromString(GetContext()->GetModeName().ToString()); + } + + return FText(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/HotPatcher/Source/HotPatcherEditor/Private/Cooker/SCookersPage.h b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/SCookersPage.h new file mode 100644 index 0000000..d356831 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/Cooker/SCookersPage.h @@ -0,0 +1,47 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" +#include "Model/FPatchersModeContext.h" +#include "Cooker/SHotPatcherCookerBase.h" +#include "SHotPatcherPageBase.h" +// engine header +#include "Interfaces/ITargetPlatform.h" +#include "Templates/SharedPointer.h" +#include "IDetailsView.h" +#include "PropertyEditorModule.h" +#include "Model/FOriginalCookerContext.h" + +/** + * Implements the profile page for the session launcher wizard. + */ +class SCookersPage + : public SHotPatcherPageBase +{ +public: + + SLATE_BEGIN_ARGS(SCookersPage) { } + SLATE_END_ARGS() + +public: + + /** + * Constructs the widget. + * + * @param InArgs The Slate argument list. + */ + void Construct( const FArguments& InArgs,TSharedPtr InContext); + virtual FString GetPageName() const override{ return TEXT("Cooker"); } + +public: + FText HandleCookerModeComboButtonContentText() const; + void HandleHotPatcherMenuEntryClicked(FString InModeName,TFunction ActionCallback); + EVisibility HandleOperatorConfigVisibility()const; + EVisibility HandleImportProjectConfigVisibility()const; +// +// private: +// TSharedPtr OriginalCookerContext; +}; diff --git a/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/AssetActions/AssetTypeActions_PrimaryAssetLabel.cpp b/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/AssetActions/AssetTypeActions_PrimaryAssetLabel.cpp new file mode 100644 index 0000000..2973ef4 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/AssetActions/AssetTypeActions_PrimaryAssetLabel.cpp @@ -0,0 +1,162 @@ +#include "AssetTypeActions_PrimaryAssetLabel.h" +#include "Engine/PrimaryAssetLabel.h" +#include "HotPatcherEditor.h" +#include "FlibAssetManageHelper.h" +#include "Misc/EngineVersionComparison.h" + +#if !UE_VERSION_OLDER_THAN(5,1,0) + typedef FAppStyle FEditorStyle; +#endif + +#if WITH_EDITOR_SECTION +#include "ToolMenuSection.h" +void FAssetTypeActions_PrimaryAssetLabel::GetActions(const TArray& InObjects, + FToolMenuSection& Section) +{ +#if !UE_VERSION_OLDER_THAN(5,1,0) + FName StyleSetName = FEditorStyle::GetAppStyleSetName(); +#else + FName StyleSetName = FEditorStyle::GetStyleSetName(); +#endif + auto Labels = GetTypedWeakObjectPtrs(InObjects); + const FSlateIcon Icon = FSlateIcon(StyleSetName, "ContentBrowser.AssetActions.Duplicate"); + Section.AddMenuEntry( + "ObjectContext_AddToPatchIncludeFilters", + NSLOCTEXT("AssetTypeActions_PrimaryAssetLabel", "ObjectContext_AddToPatchIncludeFilters", "Add To Patch Include Filters"), + NSLOCTEXT("AssetTypeActions_PrimaryAssetLabel", "ObjectContext_AddToPatchIncludeFiltersTooltip", "Add the label to HotPatcher Include Filters."), + Icon, + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_PrimaryAssetLabel::ExecuteAddToPatchIncludeFilter, Labels) + )); + Section.AddMenuEntry( + "ObjectContext_AddToChunkConfig", + NSLOCTEXT("AssetTypeActions_PrimaryAssetLabel", "ObjectContext_AddToChunkConfig", "Add To Chunk Config"), + NSLOCTEXT("AssetTypeActions_PrimaryAssetLabel", "ObjectContext_AddToChunkConfigTooltip", "Add Label To Chunk Config"), + Icon, + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_PrimaryAssetLabel::ExecuteAddToChunkConfig, Labels) + )); + Section.AddSubMenu( + "ObjectContext_CookAndPakActionsSubMenu", + NSLOCTEXT("AssetTypeActions_PrimaryAssetLabel", "ObjectContext_CookAndPakActionsSubMenu","Cook And Pak Label Actions"), + NSLOCTEXT("AssetTypeActions_PrimaryAssetLabel", "ObjectContext_CookAndPakActionsSubMenu","Cook And Pak Label Actions"), + FNewToolMenuDelegate::CreateRaw(this, &FAssetTypeActions_PrimaryAssetLabel::MakeCookAndPakActionsSubMenu,Labels), + FUIAction( + FExecuteAction() + ), + EUserInterfaceActionType::Button, + false, + FSlateIcon(StyleSetName, "ContentBrowser.SaveAllCurrentFolder") + ); + +} +TArray GetLabelsAssets(TArray> Objects) +{ + TArray LabelAsstes; + for(auto& Label:Objects) + { + if( Label->Rules.CookRule == EPrimaryAssetCookRule::NeverCook ) + continue; + for(const auto& Asset:Label->ExplicitAssets) + { + FPatcherSpecifyAsset CurrentAsset; + CurrentAsset.Asset = Asset.ToSoftObjectPath(); + CurrentAsset.bAnalysisAssetDependencies = Label->bLabelAssetsInMyDirectory; + CurrentAsset.AssetRegistryDependencyTypes.AddUnique(EAssetRegistryDependencyTypeEx::Packages); + LabelAsstes.AddUnique(CurrentAsset); + } + } + return LabelAsstes; +} + +TArray GetLabelsDirs(TArray> Objects) +{ + TArray Dirs; + for(auto& Label:Objects) + { + if(Label->bLabelAssetsInMyDirectory && (Label->Rules.CookRule != EPrimaryAssetCookRule::NeverCook)) + { + FString PathName = Label->GetPathName(); + TArray DirNames; + PathName.ParseIntoArray(DirNames,TEXT("/")); + FString FinalPath; + for(size_t index = 0;index < DirNames.Num() - 1;++index) + { + FinalPath += TEXT("/") + DirNames[index]; + } + FDirectoryPath CurrentDir; + CurrentDir.Path = FinalPath; + Dirs.Add(CurrentDir); + } + } + return Dirs; +} + +void FAssetTypeActions_PrimaryAssetLabel::ExecuteAddToPatchIncludeFilter( + TArray> Objects) +{ + FHotPatcherEditorModule::Get().OpenDockTab(); + if(GPatchSettings) + { + GPatchSettings->GetAssetScanConfigRef().IncludeSpecifyAssets.Append(GetLabelsAssets(Objects)); + GPatchSettings->GetAssetScanConfigRef().AssetIncludeFilters.Append(GetLabelsDirs(Objects)); + } +} + +TArray GetChunksByAssetLabels(TArray> Objects) +{ + TArray Chunks; + + for(const auto& Object:Objects) + { + FChunkInfo Chunk; + Chunk.AssetIgnoreFilters = GetLabelsDirs(TArray>{Object}); + Chunk.IncludeSpecifyAssets = GetLabelsAssets(TArray>{Object}); + Chunk.bAnalysisFilterDependencies = Object->Rules.bApplyRecursively; + FString LongPackageName = UFlibAssetManageHelper::PackagePathToLongPackageName(Object->GetPathName()); + { + TArray DirNames; + LongPackageName.ParseIntoArray(DirNames,TEXT("/")); + Chunk.ChunkName = DirNames[DirNames.Num()-1]; + } + } + return Chunks; +} + +void FAssetTypeActions_PrimaryAssetLabel::ExecuteAddToChunkConfig(TArray> Objects) +{ + FHotPatcherEditorModule::Get().OpenDockTab(); + if(GPatchSettings) + { + GPatchSettings->bEnableChunk = true; + GPatchSettings->ChunkInfos.Append(GetChunksByAssetLabels(Objects)); + } +} + +void FAssetTypeActions_PrimaryAssetLabel::MakeCookAndPakActionsSubMenu(UToolMenu* Menu,TArray> Objects) +{ + FToolMenuSection& Section = Menu->AddSection("CookAndPakActionsSection"); + UHotPatcherSettings* Settings = GetMutableDefault(); + Settings->ReloadConfig(); + for (auto Platform : FHotPatcherEditorModule::Get().GetAllowCookPlatforms()) + { + if(Settings->bWhiteListCookInEditor && !Settings->PlatformWhitelists.Contains(Platform)) + continue; + Section.AddMenuEntry( + FName(*THotPatcherTemplateHelper::GetEnumNameByValue(Platform)), + FText::Format(NSLOCTEXT("Platform","Platform", "{0}"), UKismetTextLibrary::Conv_StringToText(THotPatcherTemplateHelper::GetEnumNameByValue(Platform))), + FText(), + FSlateIcon(), + FUIAction( + FExecuteAction::CreateRaw(this, &FAssetTypeActions_PrimaryAssetLabel::OnCookAndPakPlatform, Platform,Objects) + ) + ); + } +} + +void FAssetTypeActions_PrimaryAssetLabel::OnCookAndPakPlatform(ETargetPlatform Platform,TArray> Objects) +{ + FHotPatcherEditorModule::Get().CookAndPakByAssetsAndFilters(GetLabelsAssets(Objects),GetLabelsDirs(Objects),TArray{Platform},true); +} + +#endif diff --git a/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/AssetActions/AssetTypeActions_PrimaryAssetLabel.h b/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/AssetActions/AssetTypeActions_PrimaryAssetLabel.h new file mode 100644 index 0000000..869fb3a --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/AssetActions/AssetTypeActions_PrimaryAssetLabel.h @@ -0,0 +1,37 @@ +#pragma once + +#if WITH_EDITOR_SECTION +#include "ETargetPlatform.h" +#include "CoreMinimal.h" +#include "Engine/StaticMesh.h" +#include "Toolkits/IToolkitHost.h" +#include "AssetTypeActions_Base.h" +#include "Engine/PrimaryAssetLabel.h" + +struct FToolMenuSection; +class FMenuBuilder; + +class FAssetTypeActions_PrimaryAssetLabel : public FAssetTypeActions_Base +{ +public: + // IAssetTypeActions Implementation + virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_PrimaryAssetLabel", "Primary Asset Label"); } + virtual FColor GetTypeColor() const override { return FColor(0, 255, 255); } + virtual UClass* GetSupportedClass() const override { return UPrimaryAssetLabel::StaticClass(); } + virtual bool HasActions( const TArray& InObjects ) const override { return true; } + virtual void GetActions(const TArray& InObjects, FToolMenuSection& Section) override; + // virtual void OpenAssetEditor( const TArray& InObjects, TSharedPtr EditWithinLevelEditor = TSharedPtr() ) override; + virtual uint32 GetCategories() override { return EAssetTypeCategories::Basic; } + // virtual class UThumbnailInfo* GetThumbnailInfo(UObject* Asset) const override; + virtual bool IsImportedAsset() const override { return true; } + // virtual void GetResolvedSourceFilePaths(const TArray& TypeAssets, TArray& OutSourceFilePaths) const override; + // End IAssetTypeActions + +private: + void ExecuteAddToPatchIncludeFilter(TArray> Objects); + void ExecuteAddToChunkConfig(TArray> Objects); + void MakeCookAndPakActionsSubMenu(class UToolMenu* Menu,TArray> Objects); + void OnCookAndPakPlatform(ETargetPlatform Platform,TArray> Objects); +}; +#endif + diff --git a/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/ReleaseSettingsDetails.cpp b/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/ReleaseSettingsDetails.cpp new file mode 100644 index 0000000..775be94 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/ReleaseSettingsDetails.cpp @@ -0,0 +1,74 @@ +#include "CreatePatch/ReleaseSettingsDetails.h" +#include "CreatePatch/FExportReleaseSettings.h" + +// engine header +#include "DetailLayoutBuilder.h" +#include "DetailCategoryBuilder.h" +#include "DetailWidgetRow.h" +#include "Widgets/Input/SButton.h" + +#define LOCTEXT_NAMESPACE "ReleaseSettingsDetails" + +TSharedRef FReleaseSettingsDetails::MakeInstance() +{ + return MakeShareable(new FReleaseSettingsDetails()); +} + +void FReleaseSettingsDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) +{ + TArray< TSharedPtr > StructBeingCustomized; + DetailBuilder.GetStructsBeingCustomized(StructBeingCustomized); + check(StructBeingCustomized.Num() == 1); + + FExportReleaseSettings* ReleaseSettingsIns = (FExportReleaseSettings*)StructBeingCustomized[0].Get()->GetStructMemory(); + + IDetailCategoryBuilder& VersionCategory = DetailBuilder.EditCategory("Version",FText::GetEmpty(),ECategoryPriority::Default); + VersionCategory.SetShowAdvanced(true); + + VersionCategory.AddCustomRow(LOCTEXT("ImportPakLists", "Import Pak Lists"),true) + .ValueContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(0) + .AutoWidth() + [ + SNew(SButton) + .Text(LOCTEXT("Import", "Import")) + .ToolTipText(LOCTEXT("ImportPakLists_Tooltip", "Import Pak Lists")) + .IsEnabled_Lambda([this,ReleaseSettingsIns]()->bool + { + return ReleaseSettingsIns->IsByPakList(); + }) + .OnClicked_Lambda([this, ReleaseSettingsIns]() + { + if (ReleaseSettingsIns) + { + ReleaseSettingsIns->ImportPakLists(); + } + return(FReply::Handled()); + }) + ] + + SHorizontalBox::Slot() + .Padding(5,0,0,0) + .AutoWidth() + [ + SNew(SButton) + .Text(LOCTEXT("Clear", "Clear")) + .ToolTipText(LOCTEXT("ClearPakLists_Tooltip", "Clear Pak Lists")) + .IsEnabled_Lambda([this,ReleaseSettingsIns]()->bool + { + return ReleaseSettingsIns->IsByPakList(); + }) + .OnClicked_Lambda([this, ReleaseSettingsIns]() + { + if (ReleaseSettingsIns) + { + ReleaseSettingsIns->ClearImportedPakList(); + } + return(FReply::Handled()); + }) + ] + ]; +} +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/SHotPatcherInformations.cpp b/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/SHotPatcherInformations.cpp new file mode 100644 index 0000000..2a1b6af --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/SHotPatcherInformations.cpp @@ -0,0 +1,56 @@ +#include "CreatePatch/SHotPatcherInformations.h" + +// engine header +#include "Widgets/Input/SHyperlink.h" +#include "Widgets/Layout/SSeparator.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Layout/SScrollBox.h" +#include "Widgets/Layout/SGridPanel.h" +#include "Widgets/Layout/SHeader.h" +#include "Internationalization/Internationalization.h" +#include "Widgets/Layout/SExpandableArea.h" + +void SHotPatcherInformations::Construct(const FArguments& InArgs) +{ + ChildSlot + [ + + SAssignNew(DiffAreaWidget, SExpandableArea) + .AreaTitle(FText::FromString(TEXT("Informations"))) + .InitiallyCollapsed(false) + .Padding(8.0) + .BodyContent() + [ + SNew(SOverlay) + + SOverlay::Slot() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Fill) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .VAlign(VAlign_Fill) + .FillWidth(1.0) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .HAlign(HAlign_Left) + .Padding(4, 4, 10, 4) + [ + SAssignNew(MulitText, SMultiLineEditableText) + .IsReadOnly(true) + ] + ] + ] + ] + ]; +} + +void SHotPatcherInformations::SetExpanded(bool InExpand) +{ + DiffAreaWidget->SetExpanded_Animated(InExpand); +} + +void SHotPatcherInformations::SetContent(const FString& InContent) +{ + MulitText->SetText(FText::FromString(InContent)); +} \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/SHotPatcherPatchWidget.cpp b/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/SHotPatcherPatchWidget.cpp new file mode 100644 index 0000000..5448909 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/SHotPatcherPatchWidget.cpp @@ -0,0 +1,681 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +// #include "HotPatcherPrivatePCH.h" +#include "CreatePatch/SHotPatcherPatchWidget.h" +#include "CreatePatch/FExportPatchSettings.h" +#include "CreatePatch/PatcherProxy.h" +#include "CreatePatch/ScopedSlowTaskContext.h" + +#include "FlibHotPatcherCoreHelper.h" +#include "FlibPatchParserHelper.h" +#include "FHotPatcherVersion.h" +#include "FlibAssetManageHelper.h" +#include "FPakFileInfo.h" +#include "ThreadUtils/FThreadUtils.hpp" +#include "HotPatcherLog.h" +#include "HotPatcherEditor.h" + +// engine header +#include "FlibHotPatcherEditorHelper.h" +#include "Misc/FileHelper.h" +#include "Widgets/Input/SHyperlink.h" +#include "Widgets/Layout/SSeparator.h" +#include "Widgets/Text/SMultiLineEditableText.h" +#include "Kismet/KismetStringLibrary.h" +#include "Kismet/KismetSystemLibrary.h" +#include "Misc/SecureHash.h" +#include "HAL/FileManager.h" +#include "PakFileUtilities.h" +#include "Kismet/KismetTextLibrary.h" +#include "Misc/EngineVersionComparison.h" + +#if !UE_VERSION_OLDER_THAN(5,1,0) + typedef FAppStyle FEditorStyle; +#endif + +#define LOCTEXT_NAMESPACE "SHotPatcherCreatePatch" + +void SHotPatcherPatchWidget::Construct(const FArguments& InArgs, TSharedPtr InCreatePatchModel) +{ + ExportPatchSetting = MakeShareable(new FExportPatchSettings); + GPatchSettings = ExportPatchSetting.Get(); + CreateExportFilterListView(); + mContext = InCreatePatchModel; + + ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .Padding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .VAlign(VAlign_Center) + [ + SettingsView->GetWidget()->AsShared() + ] + ] + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Right) + .Padding(4, 4, 10, 4) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .HAlign(HAlign_Right) + .AutoWidth() + .Padding(0, 0, 4, 0) + [ + SNew(SButton) + .Text(LOCTEXT("AddToPreset", "AddToPreset")) + .OnClicked(this, &SHotPatcherPatchWidget::DoAddToPreset) + ] + + SHorizontalBox::Slot() + .HAlign(HAlign_Right) + .AutoWidth() + .Padding(0, 0, 4, 0) + [ + SNew(SButton) + .Text(LOCTEXT("PreviewChunk", "PreviewChunk")) + .IsEnabled(this, &SHotPatcherPatchWidget::CanPreviewChunk) + .OnClicked(this, &SHotPatcherPatchWidget::DoPreviewChunk) + .Visibility(this, &SHotPatcherPatchWidget::VisibilityPreviewChunkButtons) + ] + + SHorizontalBox::Slot() + .HAlign(HAlign_Right) + .AutoWidth() + .Padding(0, 0, 4, 0) + [ + SNew(SButton) + .Text(LOCTEXT("Diff", "Diff")) + .IsEnabled(this, &SHotPatcherPatchWidget::CanDiff) + .OnClicked(this, &SHotPatcherPatchWidget::DoDiff) + .Visibility(this, &SHotPatcherPatchWidget::VisibilityDiffButtons) + ] + + SHorizontalBox::Slot() + .HAlign(HAlign_Right) + .AutoWidth() + .Padding(0, 0, 4, 0) + [ + SNew(SButton) + .Text(LOCTEXT("ClearDiff", "ClearDiff")) + .IsEnabled(this, &SHotPatcherPatchWidget::CanDiff) + .OnClicked(this, &SHotPatcherPatchWidget::DoClearDiff) + .Visibility(this, &SHotPatcherPatchWidget::VisibilityDiffButtons) + ] + + SHorizontalBox::Slot() + .HAlign(HAlign_Right) + .AutoWidth() + .Padding(0, 0, 4, 0) + [ + SNew(SButton) + .Text(LOCTEXT("PreviewPatch", "PreviewPatch")) + .IsEnabled(this, &SHotPatcherPatchWidget::CanPreviewPatch) + .OnClicked(this, &SHotPatcherPatchWidget::DoPreviewPatch) + .ToolTipText(this,&SHotPatcherPatchWidget::GetGenerateTooltipText) + ] + + SHorizontalBox::Slot() + .HAlign(HAlign_Right) + .AutoWidth() + .Padding(0, 0, 4, 0) + [ + SNew(SButton) + .Text(LOCTEXT("GeneratePatch", "GeneratePatch")) + .ToolTipText(this,&SHotPatcherPatchWidget::GetGenerateTooltipText) + .IsEnabled(this, &SHotPatcherPatchWidget::CanExportPatch) + .OnClicked(this, &SHotPatcherPatchWidget::DoExportPatch) + ] + ] + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Fill) + .VAlign(VAlign_Fill) + .Padding(4, 4, 10, 4) + [ + SAssignNew(DiffWidget, SHotPatcherInformations) + .Visibility(EVisibility::Collapsed) + ] + + ]; +} + +void SHotPatcherPatchWidget::ImportConfig() +{ + UE_LOG(LogHotPatcher, Log, TEXT("Patch Import Config")); + TArray Files = this->OpenFileDialog(); + if (!Files.Num()) return; + + FString LoadFile = Files[0]; + + FString JsonContent; + if (UFlibAssetManageHelper::LoadFileToString(LoadFile, JsonContent)) + { + THotPatcherTemplateHelper::TDeserializeJsonStringAsStruct(JsonContent,*ExportPatchSetting); + // adaptor old version config + UFlibHotPatcherCoreHelper::AdaptorOldVersionConfig(ExportPatchSetting->GetAssetScanConfigRef(),JsonContent); + SettingsView->GetDetailsView()->ForceRefresh(); + } +} + +void SHotPatcherPatchWidget::ExportConfig()const +{ + UE_LOG(LogHotPatcher, Log, TEXT("Patch Export Config")); + TArray Files = this->SaveFileDialog(); + + if (!Files.Num()) return; + + FString SaveToFile = Files[0].EndsWith(TEXT(".json")) ? Files[0] : Files[0].Append(TEXT(".json")); + + if (ExportPatchSetting) + { + if (ExportPatchSetting->IsSaveConfig()) + { + FString SerializedJsonStr; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(*ExportPatchSetting,SerializedJsonStr); + if (FFileHelper::SaveStringToFile(SerializedJsonStr, *SaveToFile)) + { + FText Msg = LOCTEXT("SavedPatchConfigMas", "Successd to Export the Patch Config."); + UFlibHotPatcherEditorHelper::CreateSaveFileNotify(Msg, SaveToFile); + } + } + } +} + +void SHotPatcherPatchWidget::ResetConfig() +{ + UE_LOG(LogHotPatcher, Log, TEXT("Patch Clear Config")); + FString DefaultSettingJson; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(*FExportPatchSettings::Get(),DefaultSettingJson); + THotPatcherTemplateHelper::TDeserializeJsonStringAsStruct(DefaultSettingJson,*ExportPatchSetting); + SettingsView->GetDetailsView()->ForceRefresh(); + +} +void SHotPatcherPatchWidget::DoGenerate() +{ + DoExportPatch(); +} + + + +bool SHotPatcherPatchWidget::InformationContentIsVisibility() const +{ + return DiffWidget->GetVisibility() == EVisibility::Visible; +} + +void SHotPatcherPatchWidget::SetInformationContent(const FString& InContent)const +{ + DiffWidget->SetContent(InContent); +} + +void SHotPatcherPatchWidget::SetInfomationContentVisibility(EVisibility InVisibility)const +{ + DiffWidget->SetVisibility(InVisibility); +} + +void SHotPatcherPatchWidget::ImportProjectConfig() +{ + SHotPatcherWidgetBase::ImportProjectConfig(); + bool bUseIoStore = false; + bool bAllowBulkDataInIoStore = false; + + GConfig->GetBool(TEXT("/Script/UnrealEd.ProjectPackagingSettings"),TEXT("bUseIoStore"),bUseIoStore,GGameIni); + GConfig->GetBool(TEXT("Core.System"),TEXT("AllowBulkDataInIoStore"),bAllowBulkDataInIoStore,GEngineIni); + + GetConfigSettings()->IoStoreSettings.bIoStore = bUseIoStore; + GetConfigSettings()->IoStoreSettings.bAllowBulkDataInIoStore = bAllowBulkDataInIoStore; + +#if ENGINE_MAJOR_VERSION > 4 + bool bMakeBinaryConfig = false; + GConfig->GetBool(TEXT("/Script/UnrealEd.ProjectPackagingSettings"),TEXT("bMakeBinaryConfig"),bMakeBinaryConfig,GEngineIni); + GetConfigSettings()->bMakeBinaryConfig = bMakeBinaryConfig; +#endif + + FString PakFileCompressionFormats; + GConfig->GetString(TEXT("/Script/UnrealEd.ProjectPackagingSettings"),TEXT("PakFileCompressionFormats"),PakFileCompressionFormats,GGameIni); + if(!PakFileCompressionFormats.IsEmpty()) + { + PakFileCompressionFormats = FString::Printf(TEXT("-compressionformats=%s"),*PakFileCompressionFormats); + GetConfigSettings()->DefaultCommandletOptions.AddUnique(PakFileCompressionFormats); + } + FString PakFileAdditionalCompressionOptions; + GConfig->GetString(TEXT("/Script/UnrealEd.ProjectPackagingSettings"),TEXT("PakFileAdditionalCompressionOptions"),PakFileAdditionalCompressionOptions,GGameIni); + + if(!PakFileAdditionalCompressionOptions.IsEmpty()) + GetConfigSettings()->DefaultCommandletOptions.AddUnique(PakFileAdditionalCompressionOptions); + +} + +void SHotPatcherPatchWidget::ShowMsg(const FString& InMsg)const +{ + auto ErrorMsgShowLambda = [this](const FString& InErrorMsg)->bool + { + bool bHasError = false; + if (!InErrorMsg.IsEmpty()) + { + this->SetInformationContent(InErrorMsg); + this->SetInfomationContentVisibility(EVisibility::Visible); + bHasError = true; + } + else + { + if (this->InformationContentIsVisibility()) + { + this->SetInformationContent(TEXT("")); + this->SetInfomationContentVisibility(EVisibility::Collapsed); + } + } + return bHasError; + }; + + ErrorMsgShowLambda(InMsg); +} + +bool SHotPatcherPatchWidget::CanDiff()const +{ + bool bCanDiff = false; + if (ExportPatchSetting) + { + bool bHasBase = !ExportPatchSetting->GetBaseVersion().IsEmpty() && FPaths::FileExists(ExportPatchSetting->GetBaseVersion()); + bool bHasVersionId = !ExportPatchSetting->GetVersionId().IsEmpty(); + bool bHasFilter = !!ExportPatchSetting->GetAssetIncludeFilters().Num(); + bool bHasSpecifyAssets = !!ExportPatchSetting->GetIncludeSpecifyAssets().Num(); + + bCanDiff = bHasBase && bHasVersionId && (bHasFilter || bHasSpecifyAssets); + } + return bCanDiff; +} +FReply SHotPatcherPatchWidget::DoDiff()const +{ + FString BaseVersionContent; + FHotPatcherVersion BaseVersion; + + bool bDeserializeStatus = false; + + if (ExportPatchSetting->IsByBaseVersion()) + { + if (UFlibAssetManageHelper::LoadFileToString(ExportPatchSetting->GetBaseVersion(), BaseVersionContent)) + { + bDeserializeStatus = THotPatcherTemplateHelper::TDeserializeJsonStringAsStruct(BaseVersionContent, BaseVersion); + } + if (!bDeserializeStatus) + { + UE_LOG(LogHotPatcher, Error, TEXT("Deserialize Base Version Faild!")); + return FReply::Handled(); + } + } + ExportPatchSetting->Init(); + FHotPatcherVersion CurrentVersion; + + // UFlibPatchParserHelper::ExportReleaseVersionInfo( + // ExportPatchSetting->GetVersionId(), + // BaseVersion.VersionId, + // FDateTime::UtcNow().ToString(), + // UFlibAssetManageHelper::DirectoryPathsToStrings(ExportPatchSetting->GetAssetIncludeFilters()), + // UFlibAssetManageHelper::DirectoryPathsToStrings(ExportPatchSetting->GetAssetIgnoreFilters()), + // ExportPatchSetting->GetAllSkipContents(), + // ExportPatchSetting->GetForceSkipClasses(), + // ExportPatchSetting->GetAssetRegistryDependencyTypes(), + // ExportPatchSetting->GetIncludeSpecifyAssets(), + // ExportPatchSetting->GetAddExternAssetsToPlatform(), + // ExportPatchSetting->IsIncludeHasRefAssetsOnly() + // ); + CurrentVersion.VersionId = ExportPatchSetting->GetVersionId(); + CurrentVersion.BaseVersionId = BaseVersion.VersionId; + CurrentVersion.Date = FDateTime::UtcNow().ToString(); + UFlibPatchParserHelper::RunAssetScanner(ExportPatchSetting->GetAssetScanConfig(),CurrentVersion); + UFlibPatchParserHelper::ExportExternAssetsToPlatform(ExportPatchSetting->GetAddExternAssetsToPlatform(),CurrentVersion,true,ExportPatchSetting->GetHashCalculator()); + + FPatchVersionDiff VersionDiffInfo = UFlibHotPatcherCoreHelper::DiffPatchVersionWithPatchSetting(*ExportPatchSetting, BaseVersion, CurrentVersion); + + bool bShowDeleteAsset = false; + FString SerializeDiffInfo; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(VersionDiffInfo,SerializeDiffInfo); + SetInformationContent(SerializeDiffInfo); + SetInfomationContentVisibility(EVisibility::Visible); + + return FReply::Handled(); +} + +FReply SHotPatcherPatchWidget::DoClearDiff()const +{ + SetInformationContent(TEXT("")); + SetInfomationContentVisibility(EVisibility::Collapsed); + + return FReply::Handled(); +} + +EVisibility SHotPatcherPatchWidget::VisibilityDiffButtons() const +{ + bool bHasBase = false; + if (ExportPatchSetting && ExportPatchSetting->IsByBaseVersion()) + { + FString BaseVersionFile = ExportPatchSetting->GetBaseVersion(); + bHasBase = !BaseVersionFile.IsEmpty() && FPaths::FileExists(BaseVersionFile); + } + + if (bHasBase && CanExportPatch()) + { + return EVisibility::Visible; + } + else { + return EVisibility::Collapsed; + } +} + + +FReply SHotPatcherPatchWidget::DoPreviewChunk() const +{ + + FHotPatcherVersion BaseVersion; + + if (ExportPatchSetting->IsByBaseVersion() && !ExportPatchSetting->GetBaseVersionInfo(BaseVersion)) + { + UE_LOG(LogHotPatcher, Error, TEXT("Deserialize Base Version Faild!")); + return FReply::Handled(); + } + else + { + // 在不进行外部文件diff的情况下清理掉基础版本的外部文件 + if (!ExportPatchSetting->IsEnableExternFilesDiff()) + { + BaseVersion.PlatformAssets.Empty(); + } + } + ExportPatchSetting->Init(); + UFlibAssetManageHelper::UpdateAssetMangerDatabase(true); + FChunkInfo NewVersionChunk = UFlibHotPatcherCoreHelper::MakeChunkFromPatchSettings(ExportPatchSetting.Get()); + + FHotPatcherVersion CurrentVersion = UFlibPatchParserHelper::ExportReleaseVersionInfoByChunk( + ExportPatchSetting->GetVersionId(), + BaseVersion.VersionId, + FDateTime::UtcNow().ToString(), + NewVersionChunk, + ExportPatchSetting->IsIncludeHasRefAssetsOnly(), + ExportPatchSetting->AssetScanConfig.bAnalysisFilterDependencies, + ExportPatchSetting->GetHashCalculator() + ); + + FString CurrentVersionSavePath = ExportPatchSetting->GetCurrentVersionSavePath(); + FPatchVersionDiff VersionDiffInfo = UFlibHotPatcherCoreHelper::DiffPatchVersionWithPatchSetting(*ExportPatchSetting, BaseVersion, CurrentVersion); + + TArray PatchChunks = ExportPatchSetting->GetChunkInfos(); + + // create default chunk + if(ExportPatchSetting->IsCreateDefaultChunk()) + { + FChunkInfo TotalChunk = UFlibPatchParserHelper::CombineChunkInfos(ExportPatchSetting->GetChunkInfos()); + + FChunkAssetDescribe ChunkDiffInfo = UFlibHotPatcherCoreHelper::DiffChunkWithPatchSetting( + *ExportPatchSetting, + NewVersionChunk, + TotalChunk + ); + if(ChunkDiffInfo.HasValidAssets()) + { + PatchChunks.Add(ChunkDiffInfo.AsChunkInfo(TEXT("Default"))); + } + } + + FString ShowMsg; + for (const auto& Chunk : PatchChunks) + { + FChunkAssetDescribe ChunkAssetsDescrible = UFlibPatchParserHelper::CollectFChunkAssetsDescribeByChunk(ExportPatchSetting.Get(), VersionDiffInfo,Chunk, ExportPatchSetting->GetPakTargetPlatforms()); + ShowMsg.Append(FString::Printf(TEXT("Chunk:%s\n"), *Chunk.ChunkName)); + auto AppendFilesToMsg = [&ShowMsg](const FString& CategoryName, const TArray& InFiles) + { + if (!!InFiles.Num()) + { + ShowMsg.Append(FString::Printf(TEXT("%s:\n"), *CategoryName)); + for (const auto& File : InFiles) + { + ShowMsg.Append(FString::Printf(TEXT("\t%s\n"), *File.ToString())); + } + } + }; + AppendFilesToMsg(TEXT("UE Assets"), ChunkAssetsDescrible.GetAssetsStrings()); + + for(auto Platform:ExportPatchSetting->GetPakTargetPlatforms()) + { + TArray PlatformExFiles; + FString PlatformName = THotPatcherTemplateHelper::GetEnumNameByValue(Platform,false); + PlatformExFiles.Append(ChunkAssetsDescrible.GetExternalFileNames(Platform)); + AppendFilesToMsg(PlatformName, PlatformExFiles); + } + AppendFilesToMsg(TEXT("Internal Files"), ChunkAssetsDescrible.GetInternalFileNames()); + ShowMsg.Append(TEXT("\n")); + } + + + if (!ShowMsg.IsEmpty()) + { + this->ShowMsg(ShowMsg); + } + return FReply::Handled(); +} + +bool SHotPatcherPatchWidget::CanPreviewChunk() const +{ + return ExportPatchSetting->IsEnableChunk(); +} + +EVisibility SHotPatcherPatchWidget::VisibilityPreviewChunkButtons() const +{ + if (CanPreviewChunk()) + { + return EVisibility::Visible; + } + else { + return EVisibility::Collapsed; + } +} +bool SHotPatcherPatchWidget::CanExportPatch()const +{ + return UFlibPatchParserHelper::IsValidPatchSettings(ExportPatchSetting.Get(),GetDefault()->bExternalFilesCheck); +} + +FReply SHotPatcherPatchWidget::DoExportPatch() +{ + TSharedPtr PatchSettings = MakeShareable(new FExportPatchSettings); + *PatchSettings = *GetConfigSettings(); + FHotPatcherEditorModule::Get().CookAndPakByPatchSettings(PatchSettings,PatchSettings->IsStandaloneMode()); + + return FReply::Handled(); +} + +FText SHotPatcherPatchWidget::GetGenerateTooltipText() const +{ + FString FinalString; + if (GetMutableDefault()->bPreviewTooltips && ExportPatchSetting) + { + bool bHasBase = false; + if (ExportPatchSetting->IsByBaseVersion()) + bHasBase = !ExportPatchSetting->GetBaseVersion().IsEmpty() && FPaths::FileExists(ExportPatchSetting->GetBaseVersion()); + else + bHasBase = true; + bool bHasVersionId = !ExportPatchSetting->GetVersionId().IsEmpty(); + bool bHasFilter = !!ExportPatchSetting->GetAssetIncludeFilters().Num(); + bool bHasSpecifyAssets = !!ExportPatchSetting->GetIncludeSpecifyAssets().Num(); + // bool bHasExternFiles = !!ExportPatchSetting->GetAddExternFiles().Num(); + // bool bHasExDirs = !!ExportPatchSetting->GetAddExternDirectory().Num(); + + bool bHasExternFiles = true; + if(GetDefault()->bExternalFilesCheck) + { + bHasExternFiles = !!ExportPatchSetting->GetAllPlatfotmExternFiles().Num(); + } + bool bHasExDirs = !!ExportPatchSetting->GetAddExternAssetsToPlatform().Num(); + bool bHasSavePath = !ExportPatchSetting->GetSaveAbsPath().IsEmpty(); + bool bHasPakPlatfotm = !!ExportPatchSetting->GetPakTargetPlatforms().Num(); + + bool bHasAnyPakFiles = ( + bHasFilter || bHasSpecifyAssets || bHasExternFiles || bHasExDirs || + ExportPatchSetting->IsIncludeEngineIni() || + ExportPatchSetting->IsIncludePluginIni() || + ExportPatchSetting->IsIncludeProjectIni() + ); + 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(bHasBase,TEXT("BaseVersion")); + AllStatus.Emplace(bHasVersionId,TEXT("HasVersionId")); + AllStatus.Emplace(bHasAnyPakFiles,TEXT("HasAnyPakFiles")); + AllStatus.Emplace(bHasPakPlatfotm,TEXT("HasPakPlatfotm")); + AllStatus.Emplace(bHasSavePath,TEXT("HasSavePath")); + + for(const auto& Status:AllStatus) + { + FinalString+=FString::Printf(TEXT("%s\n"),*Status.GetDisplay()); + } + } + return UKismetTextLibrary::Conv_StringToText(FinalString); +} + +bool SHotPatcherPatchWidget::CanPreviewPatch() const +{ + bool bHasFilter = !!ExportPatchSetting->GetAssetIncludeFilters().Num(); + bool bHasSpecifyAssets = !!ExportPatchSetting->GetIncludeSpecifyAssets().Num(); + + auto HasExFilesLambda = [this]() + { + bool result = false; + const TMap& ExFiles = ExportPatchSetting->GetAllPlatfotmExternFiles(false); + if(!!ExFiles.Num()) + { + TArray Platforms; + ExFiles.GetKeys(Platforms); + for(const auto& Platform:Platforms) + { + if(!!ExFiles.Find(Platform)->ExternFiles.Num()) + { + result=true; + break; + } + } + } + return result; + }; + bool bHasExternFiles = true; + if(GetDefault()->bExternalFilesCheck) + { + bHasExternFiles = HasExFilesLambda(); + } + + bool bHasAnyPakFiles = ( + bHasFilter || bHasSpecifyAssets || bHasExternFiles || + ExportPatchSetting->IsIncludeEngineIni() || + ExportPatchSetting->IsIncludePluginIni() || + ExportPatchSetting->IsIncludeProjectIni() + ); + + return bHasFilter || bHasSpecifyAssets || bHasExternFiles || bHasAnyPakFiles; +} + + +FReply SHotPatcherPatchWidget::DoPreviewPatch() +{ + ExportPatchSetting->Init(); + FChunkInfo DefaultChunk; + FHotPatcherVersion BaseVersion; + + if (ExportPatchSetting->IsByBaseVersion()) + { + ExportPatchSetting->GetBaseVersionInfo(BaseVersion); + DefaultChunk = UFlibHotPatcherCoreHelper::MakeChunkFromPatchVerison(BaseVersion); + if (!ExportPatchSetting->IsEnableExternFilesDiff()) + { + BaseVersion.PlatformAssets.Empty(); + } + } + + FChunkInfo NewVersionChunk = UFlibHotPatcherCoreHelper::MakeChunkFromPatchSettings(ExportPatchSetting.Get()); + + FChunkAssetDescribe ChunkAssetsDescrible = UFlibHotPatcherCoreHelper::DiffChunkByBaseVersionWithPatchSetting(*ExportPatchSetting.Get(),NewVersionChunk, DefaultChunk, BaseVersion); + + TArray AllUnselectedAssets = ChunkAssetsDescrible.GetAssetsStrings(); + TArray UnSelectedInternalFiles = ChunkAssetsDescrible.GetInternalFileNames(); + + FString TotalMsg; + auto ChunkCheckerMsg = [&TotalMsg](const FString& Category, const TArray& InAssetList) + { + if (!!InAssetList.Num()) + { + TotalMsg.Append(FString::Printf(TEXT("\n%s:\n"), *Category)); + for (const auto& Asset : InAssetList) + { + TotalMsg.Append(FString::Printf(TEXT("\t%s\n"), *Asset.ToString())); + } + } + }; + ChunkCheckerMsg(TEXT("Unreal Asset"), AllUnselectedAssets); + ChunkCheckerMsg(TEXT("External Files"), TArray{}); + for(auto Platform:ExportPatchSetting->GetPakTargetPlatforms()) + { + TArray PlatformExFiles; + FString PlatformName = THotPatcherTemplateHelper::GetEnumNameByValue(Platform,false); + PlatformExFiles.Append(ChunkAssetsDescrible.GetExternalFileNames(Platform)); + PlatformExFiles.Append(ChunkAssetsDescrible.GetExternalFileNames(ETargetPlatform::AllPlatforms)); + ChunkCheckerMsg(PlatformName, PlatformExFiles); + } + + ChunkCheckerMsg(TEXT("Internal Files"), UnSelectedInternalFiles); + + if (!TotalMsg.IsEmpty()) + { + ShowMsg(FString::Printf(TEXT("Patch Assets:\n%s"), *TotalMsg)); + return FReply::Handled(); + } + + return FReply::Handled(); +} + +FReply SHotPatcherPatchWidget::DoAddToPreset() const +{ + UHotPatcherSettings* Settings = GetMutableDefault(); + Settings->PresetConfigs.Add(*const_cast(this)->GetConfigSettings()); + Settings->SaveConfig(); + return FReply::Handled(); +} + +void SHotPatcherPatchWidget::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; + } + + FStructureDetailsViewArgs StructureViewArgs; + { + StructureViewArgs.bShowObjects = true; + StructureViewArgs.bShowAssets = true; + StructureViewArgs.bShowClasses = true; + StructureViewArgs.bShowInterfaces = true; + } + + SettingsView = EditModule.CreateStructureDetailView(DetailsViewArgs, StructureViewArgs, nullptr); + FStructOnScope* Struct = new FStructOnScope(FExportPatchSettings::StaticStruct(), (uint8*)ExportPatchSetting.Get()); + SettingsView->SetStructureData(MakeShareable(Struct)); +} + + +#undef LOCTEXT_NAMESPACE diff --git a/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/SHotPatcherReleaseWidget.cpp b/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/SHotPatcherReleaseWidget.cpp new file mode 100644 index 0000000..22e7c6e --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/SHotPatcherReleaseWidget.cpp @@ -0,0 +1,232 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +// #include "HotPatcherPrivatePCH.h" +#include "CreatePatch/SHotPatcherReleaseWidget.h" +#include "FlibHotPatcherCoreHelper.h" +#include "FlibAssetManageHelper.h" +#include "AssetManager/FAssetDependenciesInfo.h" +#include "FHotPatcherVersion.h" +#include "HotPatcherLog.h" +#include "CreatePatch/ReleaseSettingsDetails.h" +#include "CreatePatch/FExportReleaseSettings.h" +#include "HotPatcherEditor.h" + +// engine header +#include "FlibHotPatcherEditorHelper.h" +#include "CreatePatch/ReleaseProxy.h" +#include "Widgets/Input/SHyperlink.h" +#include "Widgets/Layout/SSeparator.h" +#include "HAL/FileManager.h" +#include "Kismet/KismetTextLibrary.h" +#include "Misc/FileHelper.h" +#include "Misc/ScopedSlowTask.h" +#include "Misc/EngineVersionComparison.h" + +#if !UE_VERSION_OLDER_THAN(5,1,0) + typedef FAppStyle FEditorStyle; +#endif + +#define LOCTEXT_NAMESPACE "SHotPatcherExportRelease" + +void SHotPatcherReleaseWidget::Construct(const FArguments& InArgs, TSharedPtr InCreatePatchModel) +{ + ExportReleaseSettings = MakeShareable(new FExportReleaseSettings); + GReleaseSettings = ExportReleaseSettings.Get(); + CreateExportFilterListView(); + mContext = InCreatePatchModel; + + 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("GenerateRelease", "Export Release")) + .OnClicked(this,&SHotPatcherReleaseWidget::DoExportRelease) + .IsEnabled(this,&SHotPatcherReleaseWidget::CanExportRelease) + .ToolTipText(this,&SHotPatcherReleaseWidget::GetGenerateTooltipText) + ] + ]; +} + +void SHotPatcherReleaseWidget::ImportConfig() +{ + UE_LOG(LogHotPatcher, Log, TEXT("Release Import 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,*ExportReleaseSettings); + // adaptor old version config + UFlibHotPatcherCoreHelper::AdaptorOldVersionConfig(ExportReleaseSettings->GetAssetScanConfigRef(),JsonContent); + SettingsView->GetDetailsView()->ForceRefresh(); + } +} + +void SHotPatcherReleaseWidget::ExportConfig()const +{ + UE_LOG(LogHotPatcher, Log, TEXT("Release Export Config")); + TArray Files = this->SaveFileDialog(); + + if (!Files.Num()) return; + + FString SaveToFile = Files[0].EndsWith(TEXT(".json")) ? Files[0] : Files[0].Append(TEXT(".json")); + + if (ExportReleaseSettings) + { + + FString SerializedJsonStr; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(*ExportReleaseSettings,SerializedJsonStr); + if (FFileHelper::SaveStringToFile(SerializedJsonStr, *SaveToFile)) + { + FText Msg = LOCTEXT("SavedPatchConfigMas", "Successd to Export the Patch Config."); + UFlibHotPatcherEditorHelper::CreateSaveFileNotify(Msg, SaveToFile); + } + } +} + +void SHotPatcherReleaseWidget::ResetConfig() +{ + UE_LOG(LogHotPatcher, Log, TEXT("Release Clear Config")); + FString DefaultSettingJson; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(*FExportReleaseSettings::Get(),DefaultSettingJson); + THotPatcherTemplateHelper::TDeserializeJsonStringAsStruct(DefaultSettingJson,*ExportReleaseSettings); + SettingsView->GetDetailsView()->ForceRefresh(); +} +void SHotPatcherReleaseWidget::DoGenerate() +{ + UE_LOG(LogHotPatcher, Log, TEXT("Release DoGenerate")); +} + +void SHotPatcherReleaseWidget::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(FExportReleaseSettings::StaticStruct(), (uint8*)ExportReleaseSettings.Get()); + SettingsView->GetOnFinishedChangingPropertiesDelegate().AddRaw(ExportReleaseSettings.Get(),&FExportReleaseSettings::OnFinishedChangingProperties); + SettingsView->GetDetailsView()->RegisterInstancedCustomPropertyLayout(FExportReleaseSettings::StaticStruct(),FOnGetDetailCustomizationInstance::CreateStatic(&FReleaseSettingsDetails::MakeInstance)); + SettingsView->SetStructureData(MakeShareable(Struct)); +} + +bool SHotPatcherReleaseWidget::CanExportRelease()const +{ + bool bCanExport=false; + if (ExportReleaseSettings) + { + bool bHasVersion = !ExportReleaseSettings->GetVersionId().IsEmpty(); + bool bHasFilter = !!ExportReleaseSettings->GetAssetIncludeFilters().Num(); + bool bHasSpecifyAssets = !!ExportReleaseSettings->GetSpecifyAssets().Num(); + bool bHasSavePath = !(ExportReleaseSettings->GetSaveAbsPath().IsEmpty()); + bool bHasPakInfo = !!ExportReleaseSettings->GetPlatformsPakListFiles().Num(); + bCanExport = bHasVersion && (ExportReleaseSettings->IsImportProjectSettings() || bHasFilter || bHasSpecifyAssets || bHasPakInfo) && bHasSavePath; + } + return bCanExport; +} + +FReply SHotPatcherReleaseWidget::DoExportRelease() +{ + if(!GetConfigSettings()->IsStandaloneMode()) + { + UReleaseProxy* ReleaseProxy = NewObject(); + ReleaseProxy->AddToRoot(); + ReleaseProxy->Init(ExportReleaseSettings.Get()); + ReleaseProxy->DoExport(); + } + else + { + FString CurrentConfig; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(*GetConfigSettings(),CurrentConfig); + FString SaveConfigTo = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectSavedDir(),TEXT("HotPatcher"),TEXT("ReleaseConfig.json"))); + FFileHelper::SaveStringToFile(CurrentConfig,*SaveConfigTo); + FString NoShaderCompile = GetConfigSettings()->bNoShaderCompile ? TEXT("-NoShaderCompile") : TEXT(""); + FString MissionCommand = FString::Printf(TEXT("\"%s\" -run=HotRelease -config=\"%s\" %s %s"),*UFlibPatchParserHelper::GetProjectFilePath(),*SaveConfigTo,*GetConfigSettings()->GetCombinedAdditionalCommandletArgs(),*NoShaderCompile); + UE_LOG(LogHotPatcher,Log,TEXT("HotPatcher %s Mission: %s %s"),*GetMissionName(),*UFlibHotPatcherCoreHelper::GetUECmdBinary(),*MissionCommand); + FHotPatcherEditorModule::Get().RunProcMission(UFlibHotPatcherCoreHelper::GetUECmdBinary(),MissionCommand,GetMissionName()); + } + return FReply::Handled(); +} + +FText SHotPatcherReleaseWidget::GetGenerateTooltipText() const +{ + FString FinalString; + if (GetMutableDefault()->bPreviewTooltips && ExportReleaseSettings) + { + bool bHasVersion = !ExportReleaseSettings->GetVersionId().IsEmpty(); + bool bHasFilter = !!ExportReleaseSettings->GetAssetIncludeFilters().Num(); + bool bHasSpecifyAssets = !!ExportReleaseSettings->GetSpecifyAssets().Num(); + bool bHasExternFiles = !!ExportReleaseSettings->GetAddExternAssetsToPlatform().Num(); + bool bHasPakInfo = !!ExportReleaseSettings->GetPlatformsPakListFiles().Num(); + bool bHasSavePath = !(ExportReleaseSettings->GetSaveAbsPath().IsEmpty()); + + 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(bHasVersion,TEXT("HasVersion")); + AllStatus.Emplace((ExportReleaseSettings->IsImportProjectSettings()||bHasFilter||bHasSpecifyAssets||bHasExternFiles||bHasPakInfo),TEXT("ImportProjectSettings or HasFilter or HasSpecifyAssets or bHasExternFiles or bHasPakInfo")); + AllStatus.Emplace(bHasSavePath,TEXT("HasSavePath")); + + for(const auto& Status:AllStatus) + { + FinalString+=FString::Printf(TEXT("%s\n"),*Status.GetDisplay()); + } + } + return UKismetTextLibrary::Conv_StringToText(FinalString); +} + +#undef LOCTEXT_NAMESPACE diff --git a/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/SPatchersPage.cpp b/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/SPatchersPage.cpp new file mode 100644 index 0000000..920a49e --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/CreatePatch/SPatchersPage.cpp @@ -0,0 +1,169 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "CreatePatch/SPatchersPage.h" +#include "CreatePatch/SHotPatcherPatchWidget.h" +#include "CreatePatch/SHotPatcherReleaseWidget.h" + +// engine header +#include "HotPatcherEditor.h" +#include "Framework/Commands/UIAction.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/Layout/SExpandableArea.h" + +#define LOCTEXT_NAMESPACE "SProjectCreatePatchPage" + + +/* SProjectCreatePatchPage interface + *****************************************************************************/ + +void SPatchersPage::Construct(const FArguments& InArgs, TSharedPtr InContext) +{ + SetContext(InContext); + + // create cook modes menu + FMenuBuilder PatchModeMenuBuilder(true, NULL); + TMap* Patchers = FHotPatcherActionManager::Get().GetHotPatcherActions().Find(GetPageName()); + if(Patchers) + { + for(const auto& Action:*Patchers) + { + if(FHotPatcherActionManager::Get().IsSupportEditorAction(Action.Key)) + { + FUIAction UIAction = FExecuteAction::CreateSP(this, &SPatchersPage::HandleHotPatcherMenuEntryClicked, Action.Value.ActionName.Get().ToString(),Action.Value.ActionCallback); + PatchModeMenuBuilder.AddMenuEntry(Action.Value.ActionName, Action.Value.ActionToolTip, Action.Value.Icon, UIAction); + } + } + } + + auto Widget = SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .FillWidth(1.0) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(LOCTEXT("WhichProjectToUseText", "How would you like to Create Patch the content?")) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(8.0, 0.0, 0.0, 0.0) + [ + SNew(SButton) + .Text(LOCTEXT("ImportProjectConfig", "Import Project Config")) + .OnClicked(this,&SPatchersPage::DoImportProjectConfig) + .Visibility(this,&SPatchersPage::HandleImportProjectConfigVisibility) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(8.0, 0.0, 0.0, 0.0) + [ + SNew(SButton) + .Text(LOCTEXT("ImportConfig", "Import")) + .OnClicked(this,&SPatchersPage::DoImportConfig) + .Visibility(this,&SPatchersPage::HandleOperatorConfigVisibility) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(5.0, 0.0, 0.0, 0.0) + [ + SNew(SButton) + .Text(LOCTEXT("ExportConfig", "Export")) + .OnClicked(this,&SPatchersPage::DoExportConfig) + .Visibility(this, &SPatchersPage::HandleOperatorConfigVisibility) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(5.0, 0.0, 0.0, 0.0) + [ + SNew(SButton) + .Text(LOCTEXT("ResetConfig", "Reset")) + .OnClicked(this, &SPatchersPage::DoResetConfig) + .Visibility(this, &SPatchersPage::HandleOperatorConfigVisibility) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(5.0, 0.0, 0.0, 0.0) + [ + // cooking mode menu + SNew(SComboButton) + .ButtonContent() + [ + SNew(STextBlock) + .Text(this, &SPatchersPage::HandlePatchModeComboButtonContentText) + ] + .ContentPadding(FMargin(6.0, 2.0)) + .MenuContent() + [ + PatchModeMenuBuilder.MakeWidget() + ] + ] + ]; + + if(Patchers) + { + for(const auto& Patcher:*Patchers) + { + 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); + } + } + } + + ChildSlot + [ + Widget + ]; + + if(FHotPatcherAction* DefaultAction = FHotPatcherActionManager::Get().GetTopActionByCategory(GetPageName())) + { + HandleHotPatcherMenuEntryClicked(UKismetTextLibrary::Conv_TextToString(DefaultAction->ActionName.Get()),nullptr); + } +} + +EVisibility SPatchersPage::HandleOperatorConfigVisibility()const +{ + return EVisibility::Visible; +} + +EVisibility SPatchersPage::HandleImportProjectConfigVisibility() const +{ + TArray EnableImportActions = {TEXT("ByPatch"),TEXT("ByRelease")}; + bool bEnable = EnableImportActions.Contains(GetContext()->GetModeName().ToString()); + return bEnable ? EVisibility::Visible : EVisibility::Hidden; +} + +void SPatchersPage::HandleHotPatcherMenuEntryClicked(FString InModeName,TFunction ActionCallback) +{ + if(ActionCallback) + { + ActionCallback(); + } + if (GetContext().IsValid()) + { + GetContext()->SetModeByName(FName(*InModeName)); + } +} + +FText SPatchersPage::HandlePatchModeComboButtonContentText() const +{ + FText ret; + if (GetContext().IsValid()) + { + ret = FText::FromString(GetContext()->GetModeName().ToString()); + } + return ret; +} + +#undef LOCTEXT_NAMESPACE diff --git a/HotPatcher/Source/HotPatcherEditor/Private/FlibHotPatcherEditorHelper.cpp b/HotPatcher/Source/HotPatcherEditor/Private/FlibHotPatcherEditorHelper.cpp new file mode 100644 index 0000000..b7afa31 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/FlibHotPatcherEditorHelper.cpp @@ -0,0 +1,72 @@ +#include "FlibHotPatcherEditorHelper.h" + +#include "DesktopPlatformModule.h" +#include "IDesktopPlatform.h" +#include "Async/Async.h" + +void UFlibHotPatcherEditorHelper::CreateSaveFileNotify(const FText& InMsg, const FString& InSavedFile, + SNotificationItem::ECompletionState NotifyType) +{ + AsyncTask(ENamedThreads::GameThread,[InMsg,InSavedFile,NotifyType]() + { + auto Message = InMsg; + FNotificationInfo Info(Message); + Info.bFireAndForget = true; + Info.ExpireDuration = 5.0f; + Info.bUseSuccessFailIcons = false; + Info.bUseLargeFont = false; + + const FString HyperLinkText = InSavedFile; + Info.Hyperlink = FSimpleDelegate::CreateLambda( + [](FString SourceFilePath) + { + FPlatformProcess::ExploreFolder(*SourceFilePath); + }, + HyperLinkText + ); + Info.HyperlinkText = FText::FromString(HyperLinkText); + 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/HotPatcherActionManager.cpp b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherActionManager.cpp new file mode 100644 index 0000000..196abed --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherActionManager.cpp @@ -0,0 +1,281 @@ +#include "HotPatcherActionManager.h" +#include "HotPatcherCore.h" +#include "HotPatcherEditor.h" +#include "Kismet/KismetTextLibrary.h" +#include "CreatePatch/SHotPatcherPatchWidget.h" +#include "CreatePatch/SHotPatcherReleaseWidget.h" +#include "Cooker/OriginalCooker/SOriginalCookWidget.h" +#include "SVersionUpdater/FVersionUpdaterManager.h" + +#define LOCTEXT_NAMESPACE "FHotPatcherActionManager" + +FHotPatcherActionManager FHotPatcherActionManager::Manager; + +void FHotPatcherActionManager::Init() +{ + 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); + }; + auto HotPatcherModDescMap2ChildModDesc = [](const TMap& HotPatcherModDescMap)->TArray + { + TArray result; + for(const auto& HotPatcherMod:HotPatcherModDescMap) + { + FChildModDesc ChildModDesc; + ChildModDesc.ModName = HotPatcherMod.Value.ModName; + ChildModDesc.CurrentVersion = HotPatcherMod.Value.CurrentVersion; + ChildModDesc.MinToolVersion = HotPatcherMod.Value.MinHotPatcherVersion; + 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; + }; + + FVersionUpdaterManager::Get().RequestLocalRegistedMods = [this,HotPatcherModDescMap2ChildModDesc]()->TArray + { + return HotPatcherModDescMap2ChildModDesc(ModsDesc); + }; + FVersionUpdaterManager::Get().RequestUnsupportLocalMods = [this,HotPatcherModDescMap2ChildModDesc]()->TArray + { + return HotPatcherModDescMap2ChildModDesc(UnSupportModsDesc); + }; + + SetupDefaultActions(); + + FVersionUpdaterManager::Get().RequestRemoveVersion(GRemoteVersionFile); +} + +void FHotPatcherActionManager::Shutdown() +{ + FVersionUpdaterManager::Get().Reset(); +} + +void FHotPatcherActionManager::RegisterCategory(const FHotPatcherCategory& Category) +{ + HotPatcherCategorys.Emplace(Category.CategoryName,Category); +} + +void FHotPatcherActionManager::RegisteHotPatcherAction(const FString& Category, const FString& ActionName, + const FHotPatcherAction& Action) +{ + TMap& CategoryIns = HotPatcherActions.FindOrAdd(Category); + CategoryIns.Add(ActionName,Action); + CategoryIns.ValueSort([](const FHotPatcherAction& R,const FHotPatcherAction& L)->bool + { + 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); +} + +void FHotPatcherActionManager::RegisteHotPatcherAction(const FHotPatcherActionDesc& NewAction) +{ + THotPatcherTemplateHelper::AppendEnumeraters(TArray{NewAction.ActionName}); + FHotPatcherAction NewPatcherAction + ( + *NewAction.ActionName, + *NewAction.ModName, + UKismetTextLibrary::Conv_StringToText(NewAction.ActionName), + UKismetTextLibrary::Conv_StringToText(NewAction.ToolTip), + FSlateIcon(), + nullptr, + NewAction.RequestWidgetPtr, + NewAction.Priority + ); + + FHotPatcherActionManager::Get().RegisteHotPatcherAction(NewAction.Category,NewAction.ActionName,NewPatcherAction); +} + +void FHotPatcherActionManager::RegisteHotPatcherMod(const FHotPatcherModDesc& ModDesc) +{ + if(ModDesc.MinHotPatcherVersion > GetHotPatcherVersion()) + { + UE_LOG(LogHotPatcherEdotor,Warning,TEXT("%s Min Support Version is %.1f,Current HotPatcher Version is %.1f"),*ModDesc.ModName,ModDesc.MinHotPatcherVersion,GetHotPatcherVersion()); + UnSupportModsDesc.Add(*ModDesc.ModName,ModDesc); + } + else + { + 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); + FHotPatcherAction Action; + if(CategoryIns.Contains(ActionName)) + { + Action = *CategoryIns.Find(ActionName); + CategoryIns.Remove(ActionName); + } + ActionsDesc.Remove(*ActionName); + OnHotPatcherActionUnRegisted.Broadcast(Category,ActionName,Action); +} + +float FHotPatcherActionManager::GetHotPatcherVersion() const +{ + FString Version = FString::Printf( + TEXT("%d.%d"), + FHotPatcherCoreModule::Get().GetMainVersion(), + FHotPatcherCoreModule::Get().GetPatchVersion() + ); + return UKismetStringLibrary::Conv_StringToFloat(Version); +} + +FHotPatcherAction* FHotPatcherActionManager::GetTopActionByCategory(const FString CategoryName) +{ + FHotPatcherAction* result = nullptr; + TMap* CategoryIns = HotPatcherActions.Find(CategoryName); + if(CategoryIns) + { + for(auto& Pair:*CategoryIns) + { + if(result) + { + break; + } + result = &Pair.Value; + } + } + 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()) + { + bool bActiveInRemote = false; + FRemteVersionDescrible* HotPatcherRemoteVersion = FVersionUpdaterManager::Get().GetRemoteVersionByName(TEXT("HotPatcher")); + if(HotPatcherRemoteVersion) + { + if(HotPatcherRemoteVersion->b3rdMods) + { + bActiveInRemote = true; + } + else + { + for(auto ActionCategory:HotPatcherRemoteVersion->ActiveActions) + { + if(ActionCategory.Value.Contains(*ActionName)) + { + bActiveInRemote = true; + break; + } + } + } + } + return bActiveInRemote; + } + 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 BuiltInMods; + + FHotPatcherModDesc HotPatcherCoreMod; + HotPatcherCoreMod.ModName = HOTPATCHEER_CORE_MODENAME; + HotPatcherCoreMod.bIsBuiltInMod = true; + HotPatcherCoreMod.CurrentVersion = 1.0; + HotPatcherCoreMod.Description = TEXT("Unreal Engine Asset Manage and Package Solution."); + HotPatcherCoreMod.URL = TEXT("https://imzlp.com/posts/17590/"); + HotPatcherCoreMod.UpdateURL = TEXT("https://github.com/hxhb/HotPatcher"); + + HotPatcherCoreMod.ModActions.Emplace( + TEXT("Patcher"),HOTPATCHEER_CORE_MODENAME,TEXT("ByRelease"), TEXT("Export Release ALL Asset Dependencies."), + CREATE_ACTION_WIDGET_LAMBDA(SHotPatcherReleaseWidget,TEXT("ByRelease")), + 0 + ); + 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 + ); + +#if ENABLE_ORIGINAL_COOKER + 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 + BuiltInMods.Add(HotPatcherCoreMod); + + for(const auto& Mod:BuiltInMods) + { + RegisteHotPatcherMod(Mod); + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherCommands.cpp b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherCommands.cpp new file mode 100644 index 0000000..9a64618 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherCommands.cpp @@ -0,0 +1,15 @@ +// Copyright 2019 Lipeng Zha, Inc. All Rights Reserved. + +#include "HotPatcherCommands.h" + +#define LOCTEXT_NAMESPACE "FHotPatcherModule" + +void FHotPatcherCommands::RegisterCommands() +{ + UI_COMMAND(PluginAction, "HotPatcher", "Hot Patch Manager", EUserInterfaceActionType::Button, FInputChord{}); + UI_COMMAND(CookSelectedAction, "Cook Actions...", "Cook the selected assets", EUserInterfaceActionType::Button, FInputChord{}); + UI_COMMAND(CookAndPakSelectedAction, "Cook And Pak Actions...", "Cook and Pak the selected assets", EUserInterfaceActionType::Button, FInputChord{}); + UI_COMMAND(AddToPakSettingsAction, "Add To Patch Settings...", "Add the selected assets to Patch Settings", EUserInterfaceActionType::Button, FInputChord{}); +} + +#undef LOCTEXT_NAMESPACE diff --git a/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherEditor.cpp b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherEditor.cpp new file mode 100644 index 0000000..827e890 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherEditor.cpp @@ -0,0 +1,693 @@ +// Copyright 2019 Lipeng Zha, Inc. All Rights Reserved. + +#include "HotPatcherEditor.h" +#include "HotPatcherStyle.h" +#include "HotPatcherCommands.h" +#include "SHotPatcher.h" +#include "HotPatcherSettings.h" +#include "Templates/HotPatcherTemplateHelper.hpp" +#include "Cooker/MultiCooker/SingleCookerProxy.h" +#include "CreatePatch/PatcherProxy.h" +#include "Cooker/MultiCooker/FlibHotCookerHelper.h" +#include "HotPatcherActionManager.h" +#include "FlibHotPatcherCoreHelper.h" +#include "FlibHotPatcherEditorHelper.h" +#include "HotPatcherLog.h" + +// ENGINE HEADER +#include "AssetToolsModule.h" +#include "ContentBrowserModule.h" +#include "IContentBrowserSingleton.h" +#include "Misc/MessageDialog.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "DesktopPlatformModule.h" +#include "ISettingsModule.h" +#include "LevelEditor.h" +#include "HAL/FileManager.h" +#include "Interfaces/IPluginManager.h" +#include "Kismet/KismetTextLibrary.h" +#include "PakFileUtilities.h" +#include "Misc/EngineVersionComparison.h" + +#if !UE_VERSION_OLDER_THAN(5,1,0) + typedef FAppStyle FEditorStyle; +#endif + +#if ENGINE_MAJOR_VERSION < 5 +#include "Widgets/Docking/SDockableTab.h" +#endif + +#if WITH_EDITOR_SECTION +#include "ToolMenus.h" +#include "ToolMenuDelegates.h" +#include "ContentBrowserMenuContexts.h" +#include "CreatePatch/AssetActions/AssetTypeActions_PrimaryAssetLabel.h" +#endif + +static const FName HotPatcherTabName("HotPatcher"); + +DEFINE_LOG_CATEGORY(LogHotPatcherEdotor) + +#define LOCTEXT_NAMESPACE "FHotPatcherEditorModule" + + +void MakeProjectSettingsForHotPatcher() +{ + if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) + { + SettingsModule->RegisterSettings("Project", "Plugins", "Hot Patcher", + LOCTEXT("HotPatcherSettingsName", "Hot Patcher"), + LOCTEXT("HotPatcherSettingsDescroption", "Configure the HotPatcher plugin"), + GetMutableDefault()); + } +} + +FHotPatcherEditorModule& FHotPatcherEditorModule::Get() +{ + FHotPatcherEditorModule& Module = FModuleManager::GetModuleChecked("HotPatcherEditor"); + return Module; +} + +void FHotPatcherEditorModule::StartupModule() +{ + UE_LOG(LogHotPatcherEdotor,Display,TEXT("FHotPatcherEditorModule StartupModule")); + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module + FHotPatcherStyle::Initialize(); + FHotPatcherStyle::ReloadTextures(); + FHotPatcherCommands::Register(); + + FHotPatcherActionManager::Get().Init(); + + +#if !UE_VERSION_OLDER_THAN(5,1,0) + StyleSetName = FEditorStyle::GetAppStyleSetName().ToString(); +#else + StyleSetName = FEditorStyle::GetStyleSetName().ToString(); +#endif + + + FHotPatcherDelegates::Get().GetNotifyFileGenerated().AddLambda([](FText Msg,const FString& File) + { + UFlibHotPatcherEditorHelper::CreateSaveFileNotify(Msg,File); + }); + + if(::IsRunningCommandlet()) + return; + + PRAGMA_DISABLE_DEPRECATION_WARNINGS + FCoreUObjectDelegates::OnObjectSaved.AddRaw(this,&FHotPatcherEditorModule::OnObjectSaved); + PRAGMA_ENABLE_DEPRECATION_WARNINGS + MakeProjectSettingsForHotPatcher(); + + MissionNotifyProay = NewObject(); + MissionNotifyProay->AddToRoot(); + + PluginCommands = MakeShareable(new FUICommandList); + PluginCommands->MapAction( + FHotPatcherCommands::Get().PluginAction, + FExecuteAction::CreateRaw(this, &FHotPatcherEditorModule::PluginButtonClicked), + FCanExecuteAction()); + + FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); + { + TSharedPtr MenuExtender = MakeShareable(new FExtender()); + MenuExtender->AddMenuExtension("WindowLayout", EExtensionHook::After, PluginCommands, FMenuExtensionDelegate::CreateRaw(this, &FHotPatcherEditorModule::AddMenuExtension)); + + LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender); + +#ifndef DISABLE_PLUGIN_TOOLBAR_MENU + // settings + TSharedPtr ToolbarExtender = MakeShareable(new FExtender); +#if ENGINE_MAJOR_VERSION > 4 + FName ExtensionHook = "Play"; +#else + FName ExtensionHook = "Settings"; +#endif + ToolbarExtender->AddToolBarExtension(ExtensionHook, EExtensionHook::After, PluginCommands, FToolBarExtensionDelegate::CreateRaw(this, &FHotPatcherEditorModule::AddToolbarExtension)); + + LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender); +#endif + } + + TSharedRef CommandList = LevelEditorModule.GetGlobalLevelEditorActions(); + CommandList->UnmapAction(FHotPatcherCommands::Get().CookSelectedAction); + CommandList->UnmapAction(FHotPatcherCommands::Get().CookAndPakSelectedAction); + CommandList->UnmapAction(FHotPatcherCommands::Get().AddToPakSettingsAction); + +#if WITH_EDITOR_SECTION + ExtendContentBrowserAssetSelectionMenu(); + ExtendContentBrowserPathSelectionMenu(); + + CreateRootMenu(); + + FAssetToolsModule::GetModule().Get().RegisterAssetTypeActions(MakeShareable(new FAssetTypeActions_PrimaryAssetLabel)); + +#endif +} + + + +void FHotPatcherEditorModule::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. + FHotPatcherActionManager::Get().Shutdown(); + FHotPatcherCommands::Unregister(); + FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(HotPatcherTabName); + FHotPatcherStyle::Shutdown(); +} + +void FHotPatcherEditorModule::OpenDockTab() +{ + if(!DockTab.IsValid() || !GPatchSettings) + { + PluginButtonClicked(); + } +} + +void FHotPatcherEditorModule::PluginButtonClicked() +{ + if (!DockTab.IsValid()) + { + FGlobalTabmanager::Get()->RegisterNomadTabSpawner(HotPatcherTabName, FOnSpawnTab::CreateRaw(this, &FHotPatcherEditorModule::OnSpawnPluginTab)) + .SetDisplayName(LOCTEXT("FHotPatcherTabTitle", "HotPatcher")) + .SetMenuType(ETabSpawnerMenuType::Hidden); + } + FGlobalTabmanager::Get()->InvokeTab(HotPatcherTabName); +} + +void FHotPatcherEditorModule::OnTabClosed(TSharedRef InTab) +{ + FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(HotPatcherTabName); + DockTab.Reset(); +} + +void FHotPatcherEditorModule::AddMenuExtension(FMenuBuilder& Builder) +{ + Builder.AddMenuEntry(FHotPatcherCommands::Get().PluginAction); +} + + +void FHotPatcherEditorModule::AddToolbarExtension(FToolBarBuilder& Builder) +{ + Builder.AddToolBarButton(FHotPatcherCommands::Get().PluginAction); +} + + + +TArray FHotPatcherEditorModule::GetSelectedAssetsInBrowserContent() +{ + FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked(TEXT("ContentBrowser")); + TArray AssetsData; + ContentBrowserModule.Get().GetSelectedAssets(AssetsData); + return AssetsData; +} + +TArray FHotPatcherEditorModule::GetSelectedFolderInBrowserContent() +{ + FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked(TEXT("ContentBrowser")); + TArray Folders; + ContentBrowserModule.Get().GetSelectedFolders(Folders); + return Folders; +} + + +TSharedRef FHotPatcherEditorModule::OnSpawnPluginTab(const class FSpawnTabArgs& InSpawnTabArgs) +{ + return SAssignNew(DockTab,SDockTab) + .TabRole(ETabRole::NomadTab) + .Label(LOCTEXT("HotPatcherTab", "Hot Patcher")) + .ToolTipText(LOCTEXT("HotPatcherTabTextToolTip", "Hot Patcher")) + .OnTabClosed(SDockTab::FOnTabClosedCallback::CreateRaw(this,&FHotPatcherEditorModule::OnTabClosed)) + .Clipping(EWidgetClipping::ClipToBounds) + [ + SNew(SHotPatcher) + ]; +} + + +#if WITH_EDITOR_SECTION + +void FHotPatcherEditorModule::CreateRootMenu() +{ + UToolMenu* RootMenu = UToolMenus::Get()->ExtendMenu("ContentBrowser.AddNewContextMenu"); + FToolMenuSection& RootSection = RootMenu->AddSection("HotPatcherUtilities", LOCTEXT("HotPatcherUtilities", "HotPatcherUtilities"));; + RootSection.AddSubMenu( + "PatcherPresetsActionsSubMenu", + LOCTEXT("PatcherPresetsActionsSubMenuLabel", "Preset Actions"), + LOCTEXT("PatcherPresetsActionsSubMenuToolTip", "Preset Actions"), + FNewToolMenuDelegate::CreateRaw(this, &FHotPatcherEditorModule::MakeHotPatcherPresetsActionsSubMenu), + FUIAction( + FExecuteAction() + ), + EUserInterfaceActionType::Button, + false, + FSlateIcon(*StyleSetName, "ContentBrowser.SaveAllCurrentFolder") + ); +} + +void FHotPatcherEditorModule::CreateAssetContextMenu(FToolMenuSection& InSection) +{ + InSection.AddSubMenu( + "CookActionsSubMenu", + LOCTEXT("CookActionsSubMenuLabel", "Cook Actions"), + LOCTEXT("CookActionsSubMenuToolTip", "Cook actions"), + FNewToolMenuDelegate::CreateRaw(this, &FHotPatcherEditorModule::MakeCookActionsSubMenu), + FUIAction( + FExecuteAction() + ), + EUserInterfaceActionType::Button, + false, + FSlateIcon(*StyleSetName, "ContentBrowser.AssetActions") + ); + InSection.AddSubMenu( + "CookAndPakActionsSubMenu", + LOCTEXT("CookAndPakActionsSubMenuLabel", "Cook And Pak Actions"), + LOCTEXT("CookAndPakActionsSubMenuToolTip", "Cook And Pak actions"), + FNewToolMenuDelegate::CreateRaw(this, &FHotPatcherEditorModule::MakeCookAndPakActionsSubMenu), + FUIAction( + FExecuteAction() + ), + EUserInterfaceActionType::Button, + false, + FSlateIcon(*StyleSetName, "ContentBrowser.SaveAllCurrentFolder") + ); + + // InSection.AddMenuEntry(FHotPatcherCommands::Get().AddToPakSettingsAction); + InSection.AddDynamicEntry("AddToPatchSettings", FNewToolMenuSectionDelegate::CreateLambda([this](FToolMenuSection& InSection) + { + const TAttribute Label = LOCTEXT("CookUtilities_AddToPatchSettings", "Add To Patch Settings"); + const TAttribute ToolTip = LOCTEXT("CookUtilities_AddToPatchSettingsTooltip", "Add Selected Assets To HotPatcher Patch Settings"); + const FSlateIcon Icon = FSlateIcon(*StyleSetName, "ContentBrowser.AssetActions.Duplicate"); + const FToolMenuExecuteAction UIAction = FToolMenuExecuteAction::CreateRaw(this,&FHotPatcherEditorModule::OnAddToPatchSettings); + + InSection.AddMenuEntry("CookUtilities_AddToPatchSettings", Label, ToolTip, Icon, UIAction); + })); + +} + + +void FHotPatcherEditorModule::ExtendContentBrowserAssetSelectionMenu() +{ + UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("ContentBrowser.AssetContextMenu"); + FToolMenuSection& Section = Menu->FindOrAddSection(TEXT("AssetContextCookUtilities"));//Menu->FindOrAddSection("AssetContextReferences"); + FToolMenuEntry& Entry = Section.AddDynamicEntry("AssetManagerEditorViewCommands", FNewToolMenuSectionDelegate::CreateLambda([this](FToolMenuSection& InSection) + { + UContentBrowserAssetContextMenuContext* Context = InSection.FindContext(); + if (Context) + { + CreateAssetContextMenu(InSection); + } + })); +} + +void FHotPatcherEditorModule::ExtendContentBrowserPathSelectionMenu() +{ +#if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 24 + UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("ContentBrowser.FolderContextMenu"); + FToolMenuSection& Section = Menu->FindOrAddSection("PathContextCookUtilities"); + FToolMenuEntry& Entry = Section.AddDynamicEntry("AssetManagerEditorViewCommands", FNewToolMenuSectionDelegate::CreateLambda([this](FToolMenuSection& InSection) + { + UContentBrowserFolderContext* Context = InSection.FindContext(); + if (Context) + { + CreateAssetContextMenu(InSection); + } + })); +#endif +} + +void FHotPatcherEditorModule::MakeCookActionsSubMenu(UToolMenu* Menu) +{ + FToolMenuSection& Section = Menu->AddSection("CookActionsSection"); + for (auto Platform : GetAllowCookPlatforms()) + { + Section.AddMenuEntry( + FName(*THotPatcherTemplateHelper::GetEnumNameByValue(Platform)), + FText::Format(LOCTEXT("Platform", "{0}"), UKismetTextLibrary::Conv_StringToText(THotPatcherTemplateHelper::GetEnumNameByValue(Platform))), + FText(), + FSlateIcon(), + FUIAction( + FExecuteAction::CreateRaw(this, &FHotPatcherEditorModule::OnCookPlatform, Platform) + ) + ); + } +} + +void FHotPatcherEditorModule::MakeCookAndPakActionsSubMenu(UToolMenu* Menu) +{ + FToolMenuSection& Section = Menu->AddSection("CookAndPakActionsSection"); + UHotPatcherSettings* Settings = GetMutableDefault(); + Settings->ReloadConfig(); + for (ETargetPlatform Platform : GetAllowCookPlatforms()) + { + FString PlatformName = THotPatcherTemplateHelper::GetEnumNameByValue(Platform); + FToolMenuEntry& PlatformEntry = Section.AddSubMenu(FName(*PlatformName), + FText::Format(LOCTEXT("Platform", "{0}"), UKismetTextLibrary::Conv_StringToText(THotPatcherTemplateHelper::GetEnumNameByValue(Platform))), + FText(), + FNewMenuDelegate::CreateLambda([=](FMenuBuilder& SubMenuBuilder){ + SubMenuBuilder.AddMenuEntry( + LOCTEXT("AnalysisDependencies", "AnalysisDependencies"), FText(), + FSlateIcon(*StyleSetName,TEXT("WorldBrowser.LevelsMenuBrush")), + FUIAction(FExecuteAction::CreateRaw(this, &FHotPatcherEditorModule::OnCookAndPakPlatform, Platform,true)), NAME_None, EUserInterfaceActionType::Button); + SubMenuBuilder.AddMenuEntry( + LOCTEXT("NoDependencies", "NoDependencies"), FText(), + FSlateIcon(*StyleSetName,TEXT("Level.SaveIcon16x")), + FUIAction(FExecuteAction::CreateRaw(this, &FHotPatcherEditorModule::OnCookAndPakPlatform, Platform,false)), NAME_None, EUserInterfaceActionType::Button); + } + )); + + // Section.AddMenuEntry( + // FName(*THotPatcherTemplateHelper::GetEnumNameByValue(Platform)), + // FText::Format(LOCTEXT("Platform", "{0}"), UKismetTextLibrary::Conv_StringToText(THotPatcherTemplateHelper::GetEnumNameByValue(Platform))), + // FText(), + // FSlateIcon(), + // FUIAction( + // FExecuteAction::CreateRaw(this, &FHotPatcherEditorModule::OnCookAndPakPlatform, Platform) + // ) + //); + } +} + +void FHotPatcherEditorModule::MakeHotPatcherPresetsActionsSubMenu(UToolMenu* Menu) +{ + FToolMenuSection& Section = Menu->AddSection("HotPatcherPresetsActionsSection"); + UHotPatcherSettings* Settings = GetMutableDefault(); + Settings->ReloadConfig(); + for (FExportPatchSettings PakConfig : Settings->PresetConfigs) + { + Section.AddSubMenu(FName(*PakConfig.VersionId), + FText::Format(LOCTEXT("PakExternal_VersionID", "{0}"), UKismetTextLibrary::Conv_StringToText(PakConfig.VersionId)), + FText(), + FNewMenuDelegate::CreateLambda([=](FMenuBuilder& SubMenuBuilder) + { + for (ETargetPlatform Platform : GetAllowCookPlatforms()) + { + FString PlatformName = THotPatcherTemplateHelper::GetEnumNameByValue(Platform); + SubMenuBuilder.AddMenuEntry( + FText::Format(LOCTEXT("PakPresetPlatform", "{0}"), UKismetTextLibrary::Conv_StringToText(PlatformName)), + FText(), + FSlateIcon(*StyleSetName,TEXT("WorldBrowser.LevelsMenuBrush")), + FUIAction(FExecuteAction::CreateRaw(this, &FHotPatcherEditorModule::OnPakPreset,PakConfig, Platform)), NAME_None, EUserInterfaceActionType::Button); + } + })); + } +} + +void FHotPatcherEditorModule::OnAddToPatchSettings(const FToolMenuContext& MenuContent) +{ + OpenDockTab(); + + TArray AssetsData = GetSelectedAssetsInBrowserContent(); + TArray FolderList = GetSelectedFolderInBrowserContent(); + TArray FolderDirectorys; + + for(const auto& Folder:FolderList) + { + FDirectoryPath Path; + Path.Path = Folder; + FolderDirectorys.Add(Path); + } + TArray AssetsSoftPath; + + for(const auto& AssetData:AssetsData) + { + FPatcherSpecifyAsset PatchSettingAssetElement; + FSoftObjectPath AssetObjectPath; + AssetObjectPath.SetPath(AssetData.ObjectPath.ToString()); + PatchSettingAssetElement.Asset = AssetObjectPath; + AssetsSoftPath.AddUnique(PatchSettingAssetElement); + } + GPatchSettings->GetAssetScanConfigRef().IncludeSpecifyAssets.Append(AssetsSoftPath); + GPatchSettings->GetAssetScanConfigRef().AssetIncludeFilters.Append(FolderDirectorys); +} + +void FHotPatcherEditorModule::OnPakPreset(FExportPatchSettings Config) +{ + TSharedPtr TmpPatchSettings = MakeShareable(new FExportPatchSettings); + *TmpPatchSettings = Config; + CookAndPakByPatchSettings(TmpPatchSettings,TmpPatchSettings->IsStandaloneMode()); +} + +#endif + +void FHotPatcherEditorModule::OnCookPlatform(ETargetPlatform Platform) +{ + UHotPatcherSettings* Settings = GetMutableDefault(); + Settings->ReloadConfig(); + TArray AssetsDatas = GetSelectedAssetsInBrowserContent(); + TArray SelectedFolder = GetSelectedFolderInBrowserContent(); + TArray FolderAssetDatas; + + UFlibAssetManageHelper::GetAssetDataInPaths(SelectedFolder,FolderAssetDatas); + AssetsDatas.Append(FolderAssetDatas); + + TArray AllSelectedAssetDetails; + + for(const auto& AssetData:AssetsDatas) + { + AllSelectedAssetDetails.AddUnique(UFlibAssetManageHelper::GetAssetDetailByPackageName(AssetData.PackageName.ToString())); + } + + auto CookNotifyLambda = [](const FString& PackageName,ETargetPlatform Platform,bool bSuccessed) + { + FString CookedDir = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectSavedDir(),TEXT("Cooked"))); + // FString PackageName = UFlibAssetManageHelper::PackagePathToLongPackageName(PackagePath); + FString CookedSavePath = UFlibHotPatcherCoreHelper::GetAssetCookedSavePath(CookedDir,PackageName, THotPatcherTemplateHelper::GetEnumNameByValue(Platform)); + + auto Msg = FText::Format( + LOCTEXT("CookAssetsNotify", "Cook {0} for {1} {2}!"), + UKismetTextLibrary::Conv_StringToText(PackageName), + UKismetTextLibrary::Conv_StringToText(THotPatcherTemplateHelper::GetEnumNameByValue(Platform)), + bSuccessed ? UKismetTextLibrary::Conv_StringToText(TEXT("Successfuly")):UKismetTextLibrary::Conv_StringToText(TEXT("Faild")) + ); + SNotificationItem::ECompletionState CookStatus = bSuccessed ? SNotificationItem::CS_Success:SNotificationItem::CS_Fail; + UFlibHotPatcherEditorHelper::CreateSaveFileNotify(Msg,CookedSavePath,CookStatus); + }; + + FSingleCookerSettings EmptySetting; + EmptySetting.MissionID = 0; + EmptySetting.MissionName = FString::Printf(TEXT("%s_Cooker_%d"),FApp::GetProjectName(),EmptySetting.MissionID); + EmptySetting.ShaderLibName = FString::Printf(TEXT("%s_Shader_%d"),FApp::GetProjectName(),EmptySetting.MissionID); + EmptySetting.CookTargetPlatforms = TArray{Platform}; + EmptySetting.CookAssets = AllSelectedAssetDetails; + // EmptySetting.ShaderLibName = FApp::GetProjectName(); + EmptySetting.bPackageTracker = false; + EmptySetting.ShaderOptions.bSharedShaderLibrary = false; + EmptySetting.IoStoreSettings.bIoStore = false; + EmptySetting.bSerializeAssetRegistry = false; + EmptySetting.bDisplayConfig = false; + EmptySetting.bForceCookInOneFrame = true; + EmptySetting.StorageCookedDir = FPaths::Combine(FPaths::ConvertRelativePathToFull(FPaths::ProjectSavedDir()),TEXT("Cooked")); + EmptySetting.StorageMetadataDir = FPaths::Combine(UFlibHotCookerHelper::GetCookerBaseDir(),EmptySetting.MissionName); + + USingleCookerProxy* SingleCookerProxy = NewObject(); + SingleCookerProxy->AddToRoot(); + SingleCookerProxy->Init(&EmptySetting); + SingleCookerProxy->OnAssetCooked.AddLambda([CookNotifyLambda](const FSoftObjectPath& ObjectPath,ETargetPlatform Platform,ESavePackageResult Result) + { + CookNotifyLambda(ObjectPath.GetAssetPathString(),Platform,Result == ESavePackageResult::Success); + }); + + bool bExportStatus = SingleCookerProxy->DoExport(); + SingleCookerProxy->Shutdown(); +} + +void FHotPatcherEditorModule::OnPakPreset(FExportPatchSettings Config, ETargetPlatform Platform) +{ + Config.PakTargetPlatforms.Empty(); + Config.PakTargetPlatforms.Add(Platform); + OnPakPreset(Config); +} + +void FHotPatcherEditorModule::CookAndPakByAssetsAndFilters(TArray IncludeAssets,TArray IncludePaths,TArray Platforms,bool bForceStandalone) +{ + FString Name = FDateTime::Now().ToString(); + PatchSettings = MakeShareable(new FExportPatchSettings); + *PatchSettings = MakeTempPatchSettings(Name,IncludePaths,IncludeAssets,TArray{},Platforms,true); + CookAndPakByPatchSettings(PatchSettings,bForceStandalone); +} + + +void FHotPatcherEditorModule::CookAndPakByPatchSettings(TSharedPtr InPatchSettings,bool bForceStandalone) +{ + if(bForceStandalone || InPatchSettings->IsStandaloneMode()) + { + FString CurrentConfig; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(*InPatchSettings.Get(),CurrentConfig); + FString SaveConfigTo = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectSavedDir(),TEXT("HotPatcher"),FString::Printf(TEXT("%s_PatchConfig.json"),*InPatchSettings->VersionId))); + 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,*UFlibHotPatcherCoreHelper::GetPlatformsStr(InPatchSettings->PakTargetPlatforms))); + RunProcMission(UFlibHotPatcherCoreHelper::GetUECmdBinary(),MissionCommand,InPatchSettings->VersionId,DisplayText); + } + else + { + UPatcherProxy* PatcherProxy = NewObject(); + PatcherProxy->AddToRoot(); + Proxys.Add(PatcherProxy); + PatcherProxy->Init(InPatchSettings.Get()); + PatcherProxy->DoExport(); + } +} + +void FHotPatcherEditorModule::OnCookAndPakPlatform(ETargetPlatform Platform, bool bAnalysicDependencies) +{ + UHotPatcherSettings* Settings = GetMutableDefault(); + Settings->ReloadConfig(); + + TArray AssetsDatas = GetSelectedAssetsInBrowserContent(); + TArray SelectedFolder = GetSelectedFolderInBrowserContent(); + TArray IncludePaths; + TArray IncludeAssets; + + for(auto& Folder:SelectedFolder) + { + FDirectoryPath CurrentPath; + if(Folder.StartsWith(TEXT("/All/"))) + { + Folder.RemoveFromStart(TEXT("/All")); + } + CurrentPath.Path = Folder; + IncludePaths.Add(CurrentPath); + } + + for(const auto& Asset:AssetsDatas) + { + FPatcherSpecifyAsset CurrentAsset; + CurrentAsset.Asset = Asset.ToSoftObjectPath(); + CurrentAsset.bAnalysisAssetDependencies = bAnalysicDependencies; + CurrentAsset.AssetRegistryDependencyTypes.AddUnique(EAssetRegistryDependencyTypeEx::Packages); + IncludeAssets.AddUnique(CurrentAsset); + } + CookAndPakByAssetsAndFilters(IncludeAssets,IncludePaths,TArray{Platform}); +} + +void FHotPatcherEditorModule::OnObjectSaved(UObject* ObjectSaved) +{ + if (GIsCookerLoadingPackage) + { + // This is the cooker saving a cooked package, ignore + return; + } + + UPackage* Package = ObjectSaved->GetOutermost(); + if (Package == nullptr || Package == GetTransientPackage()) + { + return; + } + + + // Register the package filename as modified. We don't use the cache because the file may not exist on disk yet at this point + const FString PackageFilename = FPackageName::LongPackageNameToFilename(Package->GetName(), Package->ContainsMap() ? FPackageName::GetMapPackageExtension() : FPackageName::GetAssetPackageExtension()); + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + AssetRegistryModule.Get().ScanFilesSynchronous(TArray{PackageFilename},false); +} + +FExportPatchSettings FHotPatcherEditorModule::MakeTempPatchSettings( + const FString& Name, + const TArray& AssetIncludeFilters, + const TArray& IncludeSpecifyAssets, + const TArray& ExternFiles, + const TArray& PakTargetPlatforms, + bool bCook +) +{ + UHotPatcherSettings* Settings = GetMutableDefault(); + Settings->ReloadConfig(); + FExportPatchSettings TempSettings = Settings->TempPatchSetting; + TempSettings.VersionId = Name; + TempSettings.GetAssetScanConfigRef().AssetIncludeFilters = AssetIncludeFilters; + TempSettings.GetAssetScanConfigRef().IncludeSpecifyAssets = IncludeSpecifyAssets; + TempSettings.AddExternAssetsToPlatform = ExternFiles; + TempSettings.bCookPatchAssets = bCook; + TempSettings.PakTargetPlatforms = PakTargetPlatforms; + TempSettings.SavePath.Path = Settings->GetTempSavedDir(); + return TempSettings; +} + +TArray FHotPatcherEditorModule::GetAllCookPlatforms() const +{ + TArray TargetPlatforms;//{ETargetPlatform::Android_ASTC,ETargetPlatform::IOS,ETargetPlatform::WindowsNoEditor}; + #if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 21 + UEnum* FoundEnum = StaticEnum(); +#else + FString EnumTypeName = ANSI_TO_TCHAR(THotPatcherTemplateHelper::GetCPPTypeName().c_str()); + UEnum* FoundEnum = FindObject(ANY_PACKAGE, *EnumTypeName, true); +#endif + if (FoundEnum) + { + for(int32 index =1;index < FoundEnum->GetMaxEnumValue();++index) + { + TargetPlatforms.AddUnique((ETargetPlatform)(index)); + } + + } + 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, const FText& NotifyTextOverride) +{ + if (mProcWorkingThread.IsValid() && mProcWorkingThread->GetThreadStatus()==EThreadStatus::Busy) + { + mProcWorkingThread->Cancel(); + } + else + { + mProcWorkingThread = MakeShareable(new FProcWorkerThread(*FString::Printf(TEXT("PakPresetThread_%s"),*MissionName), Bin, Command)); + mProcWorkingThread->ProcOutputMsgDelegate.BindUObject(MissionNotifyProay,&UMissionNotificationProxy::ReceiveOutputMsg); + mProcWorkingThread->ProcBeginDelegate.AddUObject(MissionNotifyProay,&UMissionNotificationProxy::SpawnRuningMissionNotification); + 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( + DisplayText, + LOCTEXT("RunningCookNotificationCancelButton", "Cancel"), + FText::FromString(FString::Printf(TEXT("%s Mission Finished!"),*MissionName)), + FText::FromString(FString::Printf(TEXT("%s Failed!"),*MissionName)) + ); + MissionNotifyProay->MissionCanceled.AddLambda([this]() + { + if (mProcWorkingThread.IsValid() && mProcWorkingThread->GetThreadStatus() == EThreadStatus::Busy) + { + mProcWorkingThread->Cancel(); + } + }); + + mProcWorkingThread->Execute(); + } + return mProcWorkingThread; +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FHotPatcherEditorModule, HotPatcherEditor) \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherModBaseModule.cpp b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherModBaseModule.cpp new file mode 100644 index 0000000..e92f921 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherModBaseModule.cpp @@ -0,0 +1,11 @@ +#include "HotPatcherModBaseModule.h" + +void FHotPatcherModBaseModule::StartupModule() +{ + FHotPatcherActionManager::Get().RegisteHotPatcherMod(GetModDesc()); +} + +void FHotPatcherModBaseModule::ShutdownModule() +{ + FHotPatcherActionManager::Get().UnRegisteHotPatcherMod(GetModDesc()); +} diff --git a/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherStyle.cpp b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherStyle.cpp new file mode 100644 index 0000000..4a6d816 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/HotPatcherStyle.cpp @@ -0,0 +1,69 @@ +// Copyright 2019 Lipeng Zha, Inc. All Rights Reserved. + +#include "HotPatcherStyle.h" +#include "HotPatcherEditor.h" +#include "Framework/Application/SlateApplication.h" +#include "Styling/SlateStyleRegistry.h" +#include "Slate/SlateGameResources.h" +#include "Interfaces/IPluginManager.h" + +TSharedPtr< FSlateStyleSet > FHotPatcherStyle::StyleInstance = NULL; + +void FHotPatcherStyle::Initialize() +{ + if (!StyleInstance.IsValid()) + { + StyleInstance = Create(); + FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance); + } +} + +void FHotPatcherStyle::Shutdown() +{ + FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance); + ensure(StyleInstance.IsUnique()); + StyleInstance.Reset(); +} + +FName FHotPatcherStyle::GetStyleSetName() +{ + static FName StyleSetName(TEXT("HotPatcherStyle")); + return StyleSetName; +} + +#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define TTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) +#define OTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) + +const FVector2D Icon16x16(16.0f, 16.0f); +const FVector2D Icon20x20(20.0f, 20.0f); +const FVector2D Icon40x40(40.0f, 40.0f); + +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; +} + +#undef IMAGE_BRUSH +#undef BOX_BRUSH +#undef BORDER_BRUSH +#undef TTF_FONT +#undef OTF_FONT + +void FHotPatcherStyle::ReloadTextures() +{ + if (FSlateApplication::IsInitialized()) + { + FSlateApplication::Get().GetRenderer()->ReloadTextureResources(); + } +} + +const ISlateStyle& FHotPatcherStyle::Get() +{ + return *StyleInstance; +} diff --git a/HotPatcher/Source/HotPatcherEditor/Private/MissionNotificationProxy.cpp b/HotPatcher/Source/HotPatcherEditor/Private/MissionNotificationProxy.cpp new file mode 100644 index 0000000..c5b2496 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/MissionNotificationProxy.cpp @@ -0,0 +1,130 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "MissionNotificationProxy.h" + + + +#include "Async/Async.h" +#include "Framework/Notifications/NotificationManager.h" +#include "ThreadUtils/FProcWorkerThread.hpp" +#include "Widgets/Notifications/SNotificationList.h" +DEFINE_LOG_CATEGORY_STATIC(LogMissionNotificationProxy, All, All); + +#define LOCTEXT_NAMESPACE "MissionNotificationPorxy" + +UMissionNotificationProxy::UMissionNotificationProxy(const FObjectInitializer& Initializer):Super(Initializer) +{} + + +void UMissionNotificationProxy::SetMissionName(FName NewMissionName) +{ + MissionName = NewMissionName; // TEXT("Cook"); +} + +void UMissionNotificationProxy::SetMissionNotifyText(const FText& RunningText, const FText& CancelText, + const FText& SuccessedText, const FText& FaildText) +{ + // running + RunningNotifyText = RunningText; // LOCTEXT("CookNotificationInProgress", "Cook in progress"); + // running cancel + RunningNofityCancelText = CancelText; // LOCTEXT("RunningCookNotificationCancelButton", "Cancel"); + // mission successed + MissionSuccessedNotifyText = SuccessedText; // LOCTEXT("CookSuccessedNotification", "Cook Finished!"); + // mission failed + MissionFailedNotifyText = FaildText; // LOCTEXT("CookFaildNotification", "Cook Faild!"); +} + +void UMissionNotificationProxy::ReceiveOutputMsg(FProcWorkerThread* Worker,const FString& InMsg) +{ + FString FindItem(TEXT("Display:")); + int32 Index = InMsg.Len() - InMsg.Find(FindItem) - FindItem.Len(); + + if (InMsg.Contains(TEXT("Error:"))) + { + UE_LOG(LogMissionNotificationProxy, Error, TEXT("%s"), *InMsg); + } + else if (InMsg.Contains(TEXT("Warning:"))) + { + UE_LOG(LogMissionNotificationProxy, Warning, TEXT("%s"), *InMsg.Right(Index)); + } + else + { + UE_LOG(LogMissionNotificationProxy, Display, TEXT("%s"), *InMsg); + } +} + +void UMissionNotificationProxy::SpawnRuningMissionNotification(FProcWorkerThread* ProcWorker) +{ + UMissionNotificationProxy* MissionProxy=this; + AsyncTask(ENamedThreads::GameThread, [MissionProxy]() + { + if (MissionProxy->PendingProgressPtr.IsValid()) + { + MissionProxy->PendingProgressPtr.Pin()->ExpireAndFadeout(); + } + FNotificationInfo Info(MissionProxy->RunningNotifyText); + + Info.bFireAndForget = false; + Info.Hyperlink = FSimpleDelegate::CreateStatic([](){ FGlobalTabmanager::Get()->InvokeTab(FName("OutputLog")); }); + Info.HyperlinkText = LOCTEXT("ShowOutputLogHyperlink", "Show Output Log"); + Info.ButtonDetails.Add(FNotificationButtonInfo(MissionProxy->RunningNofityCancelText, FText(), + FSimpleDelegate::CreateLambda([MissionProxy]() {MissionProxy->CancelMission(); }), + SNotificationItem::CS_Pending + )); + + MissionProxy->PendingProgressPtr = FSlateNotificationManager::Get().AddNotification(Info); + + MissionProxy->PendingProgressPtr.Pin()->SetCompletionState(SNotificationItem::CS_Pending); + MissionProxy->bRunning = true; + }); +} + +void UMissionNotificationProxy::SpawnMissionSuccessedNotification(FProcWorkerThread* ProcWorker) +{ + UMissionNotificationProxy* MissionProxy=this; + AsyncTask(ENamedThreads::GameThread, [MissionProxy]() { + TSharedPtr NotificationItem = MissionProxy->PendingProgressPtr.Pin(); + + if (NotificationItem.IsValid()) + { + NotificationItem->SetText(MissionProxy->MissionSuccessedNotifyText); + NotificationItem->SetCompletionState(SNotificationItem::CS_Success); + NotificationItem->ExpireAndFadeout(); + + MissionProxy->PendingProgressPtr.Reset(); + } + + MissionProxy->bRunning = false; + UE_LOG(LogMissionNotificationProxy, Log, TEXT("The %s Mission is Successfuly."),*MissionProxy->MissionName.ToString()); + }); +} + +void UMissionNotificationProxy::SpawnMissionFaildNotification(FProcWorkerThread* ProcWorker) +{ + UMissionNotificationProxy* MissionProxy = this; + AsyncTask(ENamedThreads::GameThread, [MissionProxy]() { + TSharedPtr NotificationItem = MissionProxy->PendingProgressPtr.Pin(); + + if (NotificationItem.IsValid()) + { + NotificationItem->SetText(MissionProxy->MissionFailedNotifyText); + NotificationItem->SetCompletionState(SNotificationItem::CS_Fail); + NotificationItem->ExpireAndFadeout(); + + MissionProxy->PendingProgressPtr.Reset(); + MissionProxy->bRunning = false; + UE_LOG(LogMissionNotificationProxy, Error, TEXT("The %s Mission is faild."),*MissionProxy->MissionName.ToString()) + } + + }); +} + +void UMissionNotificationProxy::CancelMission() +{ + MissionCanceled.Broadcast(); +} + + + +#undef LOCTEXT_NAMESPACE diff --git a/HotPatcher/Source/HotPatcherEditor/Private/SHotPatcher.cpp b/HotPatcher/Source/HotPatcherEditor/Private/SHotPatcher.cpp new file mode 100644 index 0000000..4b6506e --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/SHotPatcher.cpp @@ -0,0 +1,114 @@ +#include "SHotPatcher.h" +#include "CreatePatch/SPatchersPage.h" +#include "Cooker/SCookersPage.h" +#include "SVersionUpdater/SVersionUpdaterWidget.h" +// engine header +#include "HotPatcherCore.h" +#include "Widgets/Layout/SScrollBox.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Layout/SGridPanel.h" +#include "Widgets/Layout/SSeparator.h" +#include "Widgets/Notifications/SNotificationList.h" +#include "HAL/PlatformFile.h" +#include "Json.h" +#include "Model/FCookersModeContext.h" + +#define LOCTEXT_NAMESPACE "SHotPatcher" + +void SHotPatcher::Construct(const FArguments& InArgs) +{ + TMap HotPatcherCategorys; + HotPatcherCategorys.Add(TEXT("Cooker"),FHotPatcherCategory(TEXT("Cooker"),SNew(SCookersPage, MakeShareable(new FCookersModeContext)))); + HotPatcherCategorys.Add(TEXT("Patcher"),FHotPatcherCategory(TEXT("Patcher"),SNew(SPatchersPage, MakeShareable(new FPatchersModeContext)))); + HotPatcherCategorys.Append(FHotPatcherActionManager::Get().GetHotPatcherCategorys()); + float HotPatcherVersion = FHotPatcherActionManager::Get().GetHotPatcherVersion(); + ChildSlot + [ + SNew(SOverlay) + + SOverlay::Slot() + .HAlign(HAlign_Fill) + [ + SNew(SScrollBox) + + SScrollBox::Slot() + .Padding(0.0f, 10.0f, 8.0f, 0.0f) + [ + SNew(SVerticalBox) +#if ENABLE_UPDATER_CHECK + +SVerticalBox::Slot() + .AutoHeight() + [ + SAssignNew(VersionUpdaterWidget,SVersionUpdaterWidget) + .ToolName(FText::FromString(GToolName)) + .ToolVersion(HotPatcherVersion) + .DeveloperName(FText::FromString(TEXT("lipengzha"))) + .DeveloperWebsite(FText::FromString(TEXT("https://imzlp.com"))) + .UpdateWebsite(FText::FromString(TEXT("https://imzlp.com/posts/17590/"))) + .CurrentVersion(GToolMainVersion) + .PatchVersion(GToolPatchVersion) + ] +#endif + +SVerticalBox::Slot() + .Padding(0,10,0,0) + .AutoHeight() + [ + SAssignNew(ActionPageGridPanel,SGridPanel) + .FillColumn(1, 1.0f) + ] + ] + ] + ]; + + int32 RowNum = 0; + for(auto ActionPage:HotPatcherCategorys) + { + FString ActionCategoryName = ActionPage.Key; + if(TMap* ActionCategory = FHotPatcherActionManager::Get().GetHotPatcherActions().Find(ActionCategoryName)) + { + bool bHasActiveActions = false; + for(auto Action:*ActionCategory) + { + bHasActiveActions = FHotPatcherActionManager::Get().IsActiveAction(Action.Key); + if(bHasActiveActions) + { + break; + } + } + if(bHasActiveActions) + { + ActionPageGridPanel->AddSlot(0,RowNum) + .Padding(8.0f, 10.0f, 0.0f, 0.0f) + .VAlign(VAlign_Top) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle(*ActionCategoryName, 13)) + .Text(UKismetTextLibrary::Conv_StringToText(ActionCategoryName)) + ]; + + ActionPageGridPanel->AddSlot(1, RowNum) + .Padding(25.0f, 0.0f, 8.0f, 0.0f) + [ + ActionPage.Value.Widget + ]; + ++RowNum; + if(RowNum < (HotPatcherCategorys.Num()*2-1)) + { + ActionPageGridPanel->AddSlot(0, RowNum) + .ColumnSpan(3) + .Padding(0.0f, 16.0f) + [ + SNew(SSeparator) + .Orientation(Orient_Horizontal) + ]; + ++RowNum; + } + } + } + } +} + +TSharedPtr SHotPatcher::GetNotificationListPtr() const +{ + return NotificationListPtr; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherEditor/Private/SHotPatcher.h b/HotPatcher/Source/HotPatcherEditor/Private/SHotPatcher.h new file mode 100644 index 0000000..554dcd0 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/SHotPatcher.h @@ -0,0 +1,35 @@ +#pragma once + +#include "HotPatcherEditor.h" +#include "CoreMinimal.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" +#include "SVersionUpdater/SVersionUpdaterWidget.h" +#include "Widgets/Layout/SGridPanel.h" + +class SHotPatcher : public SCompoundWidget +{ + +public: + SLATE_BEGIN_ARGS(SHotPatcher) + {} + SLATE_END_ARGS() + +public: + /** + * Construct the widget + * + * @param InArgs A declaration from which to construct the widget + */ + void Construct(const FArguments& InArgs); + TSharedPtr GetNotificationListPtr()const; + + +private: + /** The list of active system messages */ + TSharedPtr NotificationListPtr; + // TArray CategoryPages; + TSharedPtr VersionUpdaterWidget; + TSharedPtr ActionPageGridPanel; +}; + diff --git a/HotPatcher/Source/HotPatcherEditor/Private/SHotPatcherPageBase.cpp b/HotPatcher/Source/HotPatcherEditor/Private/SHotPatcherPageBase.cpp new file mode 100644 index 0000000..dc981fb --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/SHotPatcherPageBase.cpp @@ -0,0 +1,49 @@ +#include "SHotPatcherPageBase.h" + +FReply SHotPatcherPageBase::DoImportConfig()const +{ + if (GetActiveAction().IsValid()) + { + GetActiveAction()->ImportConfig(); + } + return FReply::Handled(); +} + +FReply SHotPatcherPageBase::DoImportProjectConfig() const +{ + if (GetActiveAction().IsValid()) + { + GetActiveAction()->ImportProjectConfig(); + } + return FReply::Handled(); +} + +FReply SHotPatcherPageBase::DoExportConfig()const +{ + if (GetActiveAction().IsValid()) + { + GetActiveAction()->ExportConfig(); + } + return FReply::Handled(); +} +FReply SHotPatcherPageBase::DoResetConfig()const +{ + if (GetActiveAction().IsValid()) + { + GetActiveAction()->ResetConfig(); + } + return FReply::Handled(); +} + +TSharedPtr SHotPatcherPageBase::GetActiveAction()const +{ + TSharedPtr result; + if (GetContext().IsValid()) + { + if(ActionWidgetMap.Contains(GetContext()->GetModeName())) + { + result = *ActionWidgetMap.Find(GetContext()->GetModeName()); + } + } + return result; +} diff --git a/HotPatcher/Source/HotPatcherEditor/Private/SHotPatcherWidgetBase.cpp b/HotPatcher/Source/HotPatcherEditor/Private/SHotPatcherWidgetBase.cpp new file mode 100644 index 0000000..5512b12 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/SHotPatcherWidgetBase.cpp @@ -0,0 +1,74 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +// #include "HotPatcherPrivatePCH.h" +#include "SHotPatcherWidgetBase.h" +#include "FlibHotPatcherCoreHelper.h" +#include "FlibPatchParserHelper.h" +#include "FHotPatcherVersion.h" +#include "FlibAssetManageHelper.h" +#include "FPakFileInfo.h" +// engine header +#include "IDesktopPlatform.h" +#include "DesktopPlatformModule.h" +#include "FlibHotPatcherEditorHelper.h" + +#include "Misc/FileHelper.h" +#include "Widgets/Input/SHyperlink.h" +#include "Widgets/Layout/SSeparator.h" +#include "Widgets/Text/SMultiLineEditableText.h" + +#include "Kismet/KismetSystemLibrary.h" +#include "Misc/SecureHash.h" +#include "Misc/ScopedSlowTask.h" +#include "HAL/FileManager.h" +#include "Kismet/KismetTextLibrary.h" + +#define LOCTEXT_NAMESPACE "SHotPatcherCreatePatch" + +void SHotPatcherWidgetBase::Construct(const FArguments& InArgs, TSharedPtr InContext) +{ + SetContext(InContext); + // InitMissionNotificationProxy(); +} + +void SHotPatcherWidgetBase::ImportProjectConfig() +{ + // import uasset + UFlibHotPatcherCoreHelper::ImportProjectSettingsToScannerConfig(GetConfigSettings()->GetAssetScanConfigRef()); + UFlibHotPatcherCoreHelper::ImportProjectNotAssetDir(GetConfigSettings()->GetAddExternAssetsToPlatform(),ETargetPlatform::AllPlatforms); +} + +FText SHotPatcherWidgetInterface::GetGenerateTooltipText() const +{ + return UKismetTextLibrary::Conv_StringToText(TEXT("")); +} + +TArray SHotPatcherWidgetInterface::OpenFileDialog()const +{ + TArray SelectedFiles; + SelectedFiles = UFlibHotPatcherEditorHelper::OpenFileDialog( + LOCTEXT("OpenHotPatchConfigDialog", "Open .json").ToString(), + FString(TEXT("")), + TEXT(""), + TEXT("HotPatcher json (*.json)|*.json"), + EFileDialogFlags::None + ); + return SelectedFiles; +} + + +TArray SHotPatcherWidgetInterface::SaveFileDialog()const +{ + TArray SaveFilenames; + SaveFilenames = UFlibHotPatcherEditorHelper::SaveFileDialog( + LOCTEXT("SvedHotPatcherConfig", "Save .json").ToString(), + FString(TEXT("")), + TEXT(""), + TEXT("HotPatcher json (*.json)|*.json"), + EFileDialogFlags::None + ); + return SaveFilenames; +} + + +#undef LOCTEXT_NAMESPACE diff --git a/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/FVersionUpdaterManager.cpp b/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/FVersionUpdaterManager.cpp new file mode 100644 index 0000000..1c59d6c --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/FVersionUpdaterManager.cpp @@ -0,0 +1,156 @@ +#include "FVersionUpdaterManager.h" + +#include "FCountServerlessWrapper.h" +#include "HotPatcherCore.h" +#include "Interfaces/IHttpRequest.h" +#include "Interfaces/IHttpResponse.h" +#include "HttpModule.h" +#include "Kismet/KismetStringLibrary.h" +#include "Serialization/JsonReader.h" +#include "Serialization/JsonSerializer.h" + +DEFINE_LOG_CATEGORY_STATIC(LogVersionUpdaterManager,All,All) + +void FVersionUpdaterManager::Reset() +{ + if(HttpHeadRequest.IsValid()) + { + HttpHeadRequest->CancelRequest(); + HttpHeadRequest.Reset(); + } + Counter.Reset(); +} + +void FVersionUpdaterManager::Update() +{ + Counter = MakeShareable(new FCountServerlessWrapper); + FServerRequestInfo RequestInfo = FCountServerlessWrapper::MakeServerRequestInfo(); + auto ProjectInfo = FCountServerlessWrapper::MakeCurrentProject(); + ProjectInfo.PluginVersion = FString::Printf(TEXT("%d.%d"),GToolMainVersion,GToolPatchVersion); + if(GetDefault()->bServerlessCounter) + { + Counter->Init(RequestInfo,ProjectInfo); + Counter->Processor(); + } +} + +void FVersionUpdaterManager::RequestRemoveVersion(const FString& URL) +{ + if(HttpHeadRequest.IsValid()) + { + HttpHeadRequest->CancelRequest(); + HttpHeadRequest.Reset(); + } + HttpHeadRequest = FHttpModule::Get().CreateRequest(); + HttpHeadRequest->SetURL(URL); + HttpHeadRequest->SetVerb(TEXT("GET")); + HttpHeadRequest->OnProcessRequestComplete().BindRaw(this,&FVersionUpdaterManager::OnRequestComplete); + if (HttpHeadRequest->ProcessRequest()) + { + UE_LOG(LogVersionUpdaterManager, Log, TEXT("Request Remove Version.")); + } +} + +void FVersionUpdaterManager::OnRequestComplete(FHttpRequestPtr RequestPtr, FHttpResponsePtr ResponsePtr, bool bConnectedSuccessfully) +{ + if(!ResponsePtr.IsValid()) + { + return; + } + FString Result = ResponsePtr->GetContentAsString(); + TSharedRef> JsonReader = TJsonReaderFactory::Create(Result); + TSharedPtr JsonObject; + if (FJsonSerializer::Deserialize(JsonReader, JsonObject)) + { + TArray ToolNames; + if(JsonObject->TryGetStringArrayField(TEXT("Tools"),ToolNames)) + { + for(const auto& ToolName:ToolNames) + { + const TSharedPtr* ToolJsonObject; + if(JsonObject->TryGetObjectField(ToolName,ToolJsonObject)) + { + FRemteVersionDescrible RemoteVersion; + RemoteVersion.Version = ToolJsonObject->Get()->GetIntegerField(TEXT("Version")); + RemoteVersion.Author = ToolJsonObject->Get()->GetStringField(TEXT("Author")); + RemoteVersion.URL = ToolJsonObject->Get()->GetStringField(TEXT("URL")); + RemoteVersion.Website = ToolJsonObject->Get()->GetStringField(TEXT("Website")); + RemoteVersion.b3rdMods = ToolJsonObject->Get()->GetBoolField(TEXT("3rdMods")); + ToolJsonObject->Get()->TryGetNumberField(TEXT("PatchVersion"),RemoteVersion.PatchVersion); + + auto GetActionArray = [](const TSharedPtr& ActionObject,const FString& Name)->TSet + { + TSet result; + const TArray>& ActionValues = ActionObject->GetArrayField(Name); + for(auto ActionValue:ActionValues) + { + FString Value; + if(ActionValue.Get()->TryGetString(Value)) + { + result.Add(*Value); + } + } + return result; + }; + TArray ActionsName; + + const TSharedPtr* Actions; + if(ToolJsonObject->Get()->TryGetObjectField(TEXT("Actions"),Actions)) + { + if(Actions && (*Actions)->TryGetStringArrayField(TEXT("ActionNames"),ActionsName)) + { + for(auto Name:ActionsName) + { + 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); + + auto ReadFloatValue = [](const TSharedPtr& ModDescJsonObject,const FString& Name)->float + { + float Result = 0.f; + FString ValueStr; + if(ModDescJsonObject->TryGetStringField(Name,ValueStr)) + { + Result = UKismetStringLibrary::Conv_StringToFloat(ValueStr); + } + return Result; + }; + + ModDesc.RemoteVersion = ReadFloatValue(ModDescJsonObject,TEXT("Version")); + ModDesc.MinToolVersion = ReadFloatValue(ModDescJsonObject,TEXT("MinToolVersion")); + 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); + } + } + } + } + if(bConnectedSuccessfully && Remotes.Num()) + { + bRequestFinished = true; + for(const auto& callback:OnRequestFinishedCallback) + { + callback(); + } + } +} diff --git a/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/FVersionUpdaterManager.h b/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/FVersionUpdaterManager.h new file mode 100644 index 0000000..c9daee0 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/FVersionUpdaterManager.h @@ -0,0 +1,63 @@ +#pragma once +#include "Interfaces/IHttpRequest.h" +#include "CoreMinimal.h" +#include "Misc/RemoteConfigIni.h" +#include "FCountServerlessWrapper.h" + +struct FChildModDesc +{ + FString ModName; + float CurrentVersion = 0.f; + float RemoteVersion = 0.f; + float MinToolVersion = 0.f; + FString Description; + FString URL; + FString UpdateURL; + bool bIsBuiltInMod = false; +}; + +struct FRemteVersionDescrible +{ + FRemteVersionDescrible()=default; + FRemteVersionDescrible(const FRemteVersionDescrible&)=default; + + int32 Version; + int32 PatchVersion; + FString Author; + FString Website; + FString URL; + bool b3rdMods; + TMap> ActiveActions; + TMap ModsDesc; +}; + +struct FVersionUpdaterManager +{ + static FVersionUpdaterManager& Get() + { + static FVersionUpdaterManager Manager; + return Manager; + } + + void Reset(); + void Update(); + void RequestRemoveVersion(const FString& URL); + FRemteVersionDescrible* GetRemoteVersionByName(FName Name){ return Remotes.Find(Name); } + bool IsRequestFinished()const { return bRequestFinished; } + void AddOnFinishedCallback(TFunction callback){ OnRequestFinishedCallback.Add(callback); } +protected: + void OnRequestComplete(FHttpRequestPtr RequestPtr, FHttpResponsePtr ResponsePtr, bool bConnectedSuccessfully); + FHttpRequestPtr HttpHeadRequest; + TMap Remotes; + 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; + TFunction()> RequestUnsupportLocalMods = 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 new file mode 100644 index 0000000..c942587 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/SVersionUpdaterWidget.cpp @@ -0,0 +1,623 @@ +#include "SVersionUpdaterWidget.h" +// engine header +#include "FVersionUpdaterManager.h" +#include "HotPatcherCore.h" +#include "Widgets/Input/SHyperlink.h" +#include "Widgets/Layout/SBorder.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/Layout/SScrollBox.h" +#include "Widgets/SBoxPanel.h" +#include "HttpModule.h" +#include "Misc/FileHelper.h" +#include "Json.h" +#include "VersionUpdaterStyle.h" +#include "Interfaces/IHttpRequest.h" +#include "Interfaces/IHttpResponse.h" +#include "Interfaces/IPluginManager.h" +#include "Misc/EngineVersionComparison.h" + +#if !UE_VERSION_OLDER_THAN(5,1,0) + typedef FAppStyle FEditorStyle; +#endif + +#define LOCTEXT_NAMESPACE "VersionUpdaterWidget" + +void SChildModWidget::Construct(const FArguments& InArgs) +{ + ToolName = InArgs._ToolName.Get(); + ToolVersion = InArgs._ToolVersion.Get(); + ModDesc = InArgs._ModDesc.Get(); + + bool bIsInstalled = GetCurrentVersion() > 0.f; + bool bUnSupportMod = ModDesc.MinToolVersion > ToolVersion; + + ChildSlot + [ + SAssignNew(HorizontalBox,SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(0.0f, 0.0f, 0.0f, 0.0f) + [ + SNew(STextBlock) + .Text_Lambda([this,bIsInstalled]()->FText + { + FString DisplayStr; + if(ModDesc.bIsBuiltInMod) + { + DisplayStr = FString::Printf(TEXT("%s (Built-in Mod)"),*GetModName()); + } + else if(bIsInstalled) + { + DisplayStr = FString::Printf(TEXT("%s v%.1f"),*GetModName(),GetCurrentVersion()); + } + else if(GetRemoteVersion() > 0.f) + { + DisplayStr = FString::Printf(TEXT("%s v%.1f"),*GetModName(),GetRemoteVersion()); + } + return UKismetTextLibrary::Conv_StringToText(DisplayStr); + }) + ] + ]; + + if(!ModDesc.Description.IsEmpty()) + { + HorizontalBox->AddSlot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(10.0f, 0.0f, 0.0f, 0.0f) + [ + SNew(SHyperlink) + .Text(UKismetTextLibrary::Conv_StringToText(ModDesc.Description)) + .OnNavigate_Lambda([this]() + { + if(!ModDesc.URL.IsEmpty()) + { + FPlatformProcess::LaunchURL(*ModDesc.URL, NULL, NULL); + } + }) + ]; + } + + if(bUnSupportMod && !ModDesc.bIsBuiltInMod) + { + HorizontalBox->AddSlot() + .Padding(8.0f, 0.0f, 4.0f, 0.0f) + .AutoWidth() + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text_Lambda([this]()->FText + { + FString UnSupportDisplay = FString::Printf(TEXT("(Require %s v%.1f)"),*ToolName,ModDesc.MinToolVersion); + return UKismetTextLibrary::Conv_StringToText(UnSupportDisplay); + }) + ]; + } + // Update Version + if(!ModDesc.bIsBuiltInMod && + bIsInstalled && + !bUnSupportMod && + ModDesc.RemoteVersion > ModDesc.CurrentVersion) + { + FString LaunchURL = ModDesc.UpdateURL; + if(LaunchURL.IsEmpty()){ LaunchURL = ModDesc.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"),ModDesc.RemoteVersion))) + .OnNavigate_Lambda([LaunchURL]() + { + FPlatformProcess::LaunchURL(*LaunchURL, NULL, NULL); + }) + ]; + } + } +} + +void SChildModManageWidget::Construct(const FArguments& InArgs) +{ + ToolName = InArgs._ToolName.Get(); + ToolVersion = InArgs._ToolVersion.Get(); + bShowPayInfo = InArgs._bShowPayInfo.Get(); + + ChildSlot + [ + SNew(SVerticalBox) + +SVerticalBox::Slot() + .VAlign(VAlign_Center) + .AutoHeight() + [ + 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"); + } + }) + ] + ] + +SVerticalBox::Slot() + .VAlign(VAlign_Center) + .AutoHeight() + [ + SAssignNew(ChildModBorder,SBorder) + .BorderImage(FVersionUpdaterStyle::GetBrush("Updater.GroupBorder")) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .VAlign(VAlign_Top) + .HAlign(HAlign_Fill) + .Padding(5,4,20,4) + [ + SAssignNew(ChildModBox,SVerticalBox) + ] + +SHorizontalBox::Slot() + .VAlign(VAlign_Center) + .Padding(20,4,20,4) + .AutoWidth() + [ + SAssignNew(PayBox,SVerticalBox) + ] + ] + ] + ]; + if(bShowPayInfo) + { + PayBox->AddSlot() + .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.")); }) + ]; + PayBox->AddSlot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .FillHeight(1.0) + .Padding(0.f,2.f,0.f,0.f) + [ + SNew(SBox) + [ + SAssignNew(PayImage,SImage) + .Image(FVersionUpdaterStyle::GetBrush("Updater.WechatPay")) + ] + ]; + + PayBox->AddSlot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .AutoHeight() + .Padding(0.f,2.f,0.f,0.f) + [ + SAssignNew(PaymentButtonWrapper,SHorizontalBox) + ]; + AddPayment(TEXT("WechatPay"),"Updater.WechatPay"); + AddPayment(TEXT("AliPay"),"Updater.AliPay"); + SetPaymentFocus(TEXT("WechatPay")); + } +} + +bool SChildModManageWidget::AddModCategory(const FString& Category,const TArray& ModsDesc) +{ + bool bStatus = false; + if(ChildModBox.IsValid() && ModsDesc.Num()) + { + ChildModBox.Get()->AddSlot() + .AutoHeight() + .VAlign(EVerticalAlignment::VAlign_Center) + .Padding(5.f,2.f,0.f,2.f) + [ + SNew(STextBlock) + .ColorAndOpacity(FColor::Green) + .Text(UKismetTextLibrary::Conv_StringToText(Category)) + ]; + + for(const auto& ModDesc:ModsDesc) + { + AddChildMod(ModDesc); + } + bStatus = true; + } + return bStatus; +} + +bool SChildModManageWidget::AddChildMod(const FChildModDesc& ModDesc) +{ + bool bStatus = false; + if(ChildModBox.IsValid()) + { + ChildModBox.Get()->AddSlot() + .AutoHeight() + .VAlign(EVerticalAlignment::VAlign_Center) + .Padding(15.f,2.f,0.f,2.f) + [ + SNew(SChildModWidget) + .ToolName(ToolName) + .ToolVersion(ToolVersion) + .ModDesc(ModDesc) + ]; + bStatus = true; + } + return bStatus; +} + +bool SChildModManageWidget::AddPayment(const FString& Name, const FString& ImageBrushName) +{ + bool bStatus = false; + if(PaymentButtonWrapper.IsValid()) + { + TSharedPtr TmpPayTextBlock = nullptr; + SChildModManageWidget::FPaymentInfo PaymentInfo; + PaymentInfo.Name = Name; + PaymentInfo.BrushName = ImageBrushName; + + PaymentButtonWrapper->AddSlot() + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SAssignNew(PaymentInfo.Button,SButton) + .ButtonColorAndOpacity(FLinearColor::Transparent) + .OnClicked_Lambda([this,Name]()->FReply + { + SetPaymentFocus(Name); + return FReply::Handled(); + }) + [ + SAssignNew(PaymentInfo.TextBlock,STextBlock) + .ColorAndOpacity(FLinearColor::White) + .Text_Lambda([Name]()->FText{ return UKismetTextLibrary::Conv_StringToText(Name); }) + ] + ]; + + PaymentInfoMap.Add(Name,PaymentInfo); + bStatus = true; + } + return bStatus; +} + +void SChildModManageWidget::SetPaymentFocus(const FString& Name) +{ + for(const auto& PaymentInfo:PaymentInfoMap) + { + PaymentInfo.Value.TextBlock->SetColorAndOpacity(FLinearColor::White); + } + + if(PaymentInfoMap.Contains(Name)) + { + SChildModManageWidget::FPaymentInfo& FocusPaymentInfo = *PaymentInfoMap.Find(Name); + FocusPaymentInfo.TextBlock->SetColorAndOpacity(FColor::Orange); + PayImage->SetImage(FVersionUpdaterStyle::GetBrush(*FocusPaymentInfo.BrushName)); + } +} + +void SVersionUpdaterWidget::Construct(const FArguments& InArgs) +{ + static bool GBrushInited = false; + if(!GBrushInited) + { + FVersionUpdaterStyle::Initialize(FString::Printf(TEXT("%s_UpdaterStyle"),*GToolName)); + FVersionUpdaterStyle::ReloadTextures(); + GBrushInited = true; + } + + SetToolUpdateInfo( + InArgs._ToolName.Get().ToString(), + InArgs._DeveloperName.Get().ToString(), + InArgs._DeveloperWebsite.Get().ToString(), + InArgs._UpdateWebsite.Get().ToString() + ); + ToolVersion = InArgs._ToolVersion.Get(); + CurrentVersion = InArgs._CurrentVersion.Get(); + PatchVersion = InArgs._PatchVersion.Get(); + bool bhPayment = FPaths::FileExists(IPluginManager::Get().FindPlugin(ToolName)->GetBaseDir() / TEXT("Resources/hPayment.txt")); + bool bhMods = FPaths::FileExists(IPluginManager::Get().FindPlugin(ToolName)->GetBaseDir() / TEXT("Resources/hMods.txt")); + + ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .VAlign(VAlign_Center) + .AutoHeight() + [ + SNew(SBorder) + .Padding(2) + .BorderImage(FVersionUpdaterStyle::GetBrush("Updater.GroupBorder")) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + .Padding(4.0f, 0.0f, 4.0f, 0.0f) + [ + 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) + .VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .Padding(2, 4, 2, 4) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SHyperlink) + .Text_Raw(this,&SVersionUpdaterWidget::GetToolName) + .OnNavigate(this, &SVersionUpdaterWidget::HyLinkClickEventOpenUpdateWebsite) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0) + [ + SNew(SOverlay) + ] + ] + + 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) + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + 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) + ] + ] + ] + + SVerticalBox::Slot() + .VAlign(VAlign_Center) + .Padding(0,4,0,4) + .AutoHeight() + [ + SAssignNew(ChildModManageWidget,SChildModManageWidget) + .ToolName(ToolName) + .ToolVersion(ToolVersion) + .bShowPayInfo(!bhPayment) + .Visibility_Lambda([bhMods]()->EVisibility{ + EVisibility Visibility = EVisibility::Visible; + if(bhMods) { Visibility = EVisibility::Collapsed; } + return Visibility; + }) + ] + ]; + if(!FVersionUpdaterManager::Get().IsRequestFinished()) + { + FVersionUpdaterManager::Get().AddOnFinishedCallback([&]() + { + OnRemoveVersionFinished(); + }); + + FVersionUpdaterManager::Get().RequestRemoveVersion(GRemoteVersionFile); + } + else + { + OnRemoveVersionFinished(); + } + + FVersionUpdaterManager::Get().Update(); +} + +void SVersionUpdaterWidget::HyLinkClickEventOpenUpdateWebsite() +{ + FPlatformProcess::LaunchURL(*UpdateWebsite, NULL, NULL); +} + +void SVersionUpdaterWidget::HyLinkClickEventOpenDeveloperWebsite() +{ + FPlatformProcess::LaunchURL(*DeveloperWebsite, NULL, NULL); +} + +void SVersionUpdaterWidget::SetToolUpdateInfo(const FString& InToolName, const FString& InDeveloperName, + const FString& InDeveloperWebsite, const FString& InUpdateWebsite) +{ + ToolName = InToolName; + DeveloperName = InDeveloperName; + DeveloperWebsite = InDeveloperWebsite; + UpdateWebsite = InUpdateWebsite; +} + + +void SVersionUpdaterWidget::OnRemoveVersionFinished() +{ + FRemteVersionDescrible* ToolRemoteVersion = FVersionUpdaterManager::Get().GetRemoteVersionByName(*GetToolName().ToString()); + if(ToolRemoteVersion) + { + 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; + + TArray RemoteOfficalMods; + for(const auto& ModDesc:RemoteVersion.ModsDesc) + { + RemoteOfficalMods.Add(ModDesc.Value); + } + + auto GetChildModsByCondition = [this](const TArray& ModsDesc,TFunction Condition = [](const FChildModDesc&)->bool{return true;}) ->TArray + { + TArray ChildMods; + for(const auto& ModDesc:ModsDesc) + { + if(Condition && Condition(ModDesc)) + { + ChildMods.Add(ModDesc); + } + } + return ChildMods; + }; + + auto DiffMods = [](const TArray& L,const TArray& R,bool bReverse = true)->TArray + { + TArray ResultMods; + TArray LocalMods = FVersionUpdaterManager::Get().RequestLocalRegistedMods(); + for(const auto& LMod:L) + { + bool bContainInR = false; + for(const auto& RMod:R) + { + if(RMod.ModName.Equals(LMod.ModName)){ bContainInR = true;break; } + } + if(bReverse) + { + bContainInR = !bContainInR; + } + if(bContainInR) { ResultMods.Add(LMod); } + } + return ResultMods; + }; + + TArray InstalledLocalMods; + if(FVersionUpdaterManager::Get().RequestLocalRegistedMods) + { + InstalledLocalMods = FVersionUpdaterManager::Get().RequestLocalRegistedMods(); + } + + TArray BuiltInMods = GetChildModsByCondition(InstalledLocalMods,[](const FChildModDesc& Desc)->bool { return Desc.bIsBuiltInMod;}); + TArray NotBuiltInMods = GetChildModsByCondition(InstalledLocalMods,[](const FChildModDesc& Desc)->bool { return !Desc.bIsBuiltInMod;}); + TArray ThirdPartyMods = DiffMods(NotBuiltInMods,RemoteOfficalMods); + TArray InstalledOfficalMods = DiffMods(NotBuiltInMods,ThirdPartyMods); + TArray NotInstalledOfficalMods = DiffMods(RemoteOfficalMods,InstalledLocalMods); + TArray UnSupportLocalMods; + if(FVersionUpdaterManager::Get().RequestUnsupportLocalMods) + { + UnSupportLocalMods = FVersionUpdaterManager::Get().RequestUnsupportLocalMods(); + } + // Built-in Mods + ChildModManageWidget->AddModCategory(TEXT("Built-In"),DiffMods(RemoteOfficalMods,BuiltInMods,false)); + // Not Installed Offical Mod + ChildModManageWidget->AddModCategory(TEXT("Installed"),DiffMods(RemoteOfficalMods,InstalledOfficalMods,false)); + // local 3rd mods + ChildModManageWidget->AddModCategory(TEXT("ThirdParty"),ThirdPartyMods); + // Unsupport Mods (MinToolVersion > CurrentToolMod) + ChildModManageWidget->AddModCategory(TEXT("Unsupport Mods"),UnSupportLocalMods); + // Not-Installed Offical Mod + ChildModManageWidget->AddModCategory(TEXT("Not-Installed (Professional Mods)"),NotInstalledOfficalMods); + } +} + +DEFINE_LOG_CATEGORY_STATIC(LogVersionUpdater, All, All); + +#undef LOCTEXT_NAMESPACE diff --git a/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/SVersionUpdaterWidget.h b/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/SVersionUpdaterWidget.h new file mode 100644 index 0000000..0cac8f2 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/SVersionUpdaterWidget.h @@ -0,0 +1,156 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Input/Reply.h" +#include "Interfaces/IHttpRequest.h" +#include "Widgets/SCompoundWidget.h" + +#include "FVersionUpdaterManager.h" + +class SChildModWidget : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SChildModWidget): + _ToolName(), + _ToolVersion(), + _ModDesc() + {} + SLATE_ATTRIBUTE( FString, ToolName ) + SLATE_ATTRIBUTE( float, ToolVersion ) + SLATE_ATTRIBUTE( FChildModDesc, ModDesc ) + 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 ModDesc.CurrentVersion; } + float GetRemoteVersion()const { return ModDesc.RemoteVersion; } + FString GetModName()const { return ModDesc.ModName; }; + +private: + FString ToolName; + float ToolVersion = 0.f; + FChildModDesc ModDesc; + TSharedPtr HorizontalBox; +}; + +class SChildModManageWidget : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SChildModManageWidget): + _ToolName(), + _ToolVersion(), + _bShowPayInfo() + {} + SLATE_ATTRIBUTE( FString, ToolName ) + SLATE_ATTRIBUTE( float, ToolVersion ) + SLATE_ATTRIBUTE( bool, bShowPayInfo ) + SLATE_END_ARGS() + +public: + void Construct(const FArguments& InArgs); + bool AddModCategory(const FString& Category,const TArray& ModsDesc); + bool AddChildMod(const FChildModDesc& ModDesc); + bool AddPayment(const FString& Name,const FString& ImageBrushName); + void SetPaymentFocus(const FString& Name); + +protected: + FString ToolName; + float ToolVersion = 0.f; + bool bShowPayInfo = true; + + // child mod + TSharedPtr ExpanderButton; + TSharedPtr ChildModBorder; + TSharedPtr ChildModBox; + // payment + TSharedPtr PayBox; + TSharedPtr PaymentButtonWrapper; + TSharedPtr PayImage; + + struct FPaymentInfo + { + FString Name; + FString BrushName; + TSharedPtr Button; + TSharedPtr TextBlock; + }; + TMap PaymentInfoMap; +}; + +class SVersionUpdaterWidget : public SCompoundWidget +{ + +public: + SLATE_BEGIN_ARGS(SVersionUpdaterWidget): + _CurrentVersion(), + _ToolName(), + _ToolVersion(), + _DeveloperName(), + _DeveloperWebsite(), + _UpdateWebsite() + {} + SLATE_ATTRIBUTE( int32, CurrentVersion ) + SLATE_ATTRIBUTE( int32, PatchVersion ) + SLATE_ATTRIBUTE( FText, ToolName ) + SLATE_ATTRIBUTE( float, ToolVersion ) + SLATE_ATTRIBUTE( FText, DeveloperName ) + SLATE_ATTRIBUTE( FText, DeveloperWebsite ) + SLATE_ATTRIBUTE( FText, UpdateWebsite ) + SLATE_END_ARGS() + +public: + /** + * Construct the widget + * + * @param InArgs A declaration from which to construct the widget + */ + void Construct(const FArguments& InArgs); + void HyLinkClickEventOpenUpdateWebsite(); + void HyLinkClickEventOpenDeveloperWebsite(); + + FText GetCurrentVersionText() const + { + return FText::FromString( + FString::Printf(TEXT("Current Version v%d.%d"),GetCurrentVersion(),GetPatchVersion()) + ); + }; + FText GetToolName() const {return FText::FromString(ToolName);}; + FText GetDeveloperName() const {return FText::FromString(DeveloperName);}; + FText GetUpdateWebsite() const {return FText::FromString(UpdateWebsite);}; + FText GetDeveloperWebsite() const {return FText::FromString(DeveloperWebsite);}; + FText GetDeveloperDescrible() const {return FText::FromString(FString::Printf(TEXT("Developed by %s"),*GetDeveloperName().ToString()));}; + FText GetLatstVersionText() const {return FText::FromString(FString::Printf(TEXT("A new version v%d.%d is avaliable"),RemoteVersion.Version,RemoteVersion.PatchVersion));}; + 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; } + + +private: + void OnRemoveVersionFinished(); + + int32 CurrentVersion = 0; + int32 PatchVersion = 0; + FString ToolName; + float ToolVersion = 0.f; + FString UpdateWebsite; + FString DeveloperWebsite; + FString DeveloperName; + TSharedPtr UpdateInfoWidget; + FRemteVersionDescrible RemoteVersion; + TSharedPtr ChildModManageWidget; +}; + diff --git a/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/VersionUpdaterStyle.cpp b/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/VersionUpdaterStyle.cpp new file mode 100644 index 0000000..a33537b --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/VersionUpdaterStyle.cpp @@ -0,0 +1,99 @@ +// Copyright 2019 Lipeng Zha, Inc. All Rights Reserved. + +#include "VersionUpdaterStyle.h" +#include "Framework/Application/SlateApplication.h" +#include "Styling/SlateStyleRegistry.h" +#include "Interfaces/IPluginManager.h" +#include "Misc/EngineVersionComparison.h" + +TSharedPtr< FSlateStyleSet > FVersionUpdaterStyle::StyleInstance = NULL; + +void FVersionUpdaterStyle::Initialize(const FString& Name) +{ + if (!StyleInstance.IsValid()) + { + StyleInstance = Create(Name); + FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance); + } +} + +void FVersionUpdaterStyle::Shutdown() +{ + FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance); + ensure(StyleInstance.IsUnique()); + StyleInstance.Reset(); +} + +FName FVersionUpdaterStyle::GetStyleSetName() +{ + static FName StyleSetName(TEXT("VersionUpdaterStyle")); + return StyleSetName; +} + +#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define IMAGE_CORE_BRUSH( RelativePath, ... ) FSlateImageBrush( Style->RootToCoreContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define TTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) +#define OTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) + +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_IconPay(120.0f, 120.0f); + +TSharedRef< FSlateStyleSet > FVersionUpdaterStyle::Create(const FString& Name) +{ + TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet(*Name)); + Style->SetContentRoot( FPaths::EngineContentDir() / TEXT("Slate") ); + Style->SetCoreContentRoot( FPaths::EngineContentDir() / TEXT("Editor/Slate") ); + + Style->Set("Updater.GroupBorder", new BOX_BRUSH("Common/GroupBorder", FMargin(4.0f/16.0f))); + +#if !UE_VERSION_OLDER_THAN(5,1,0) + Style->Set("Updater.QuickLaunch", new IMAGE_BRUSH("Launcher/Launcher_Launch", Updater_Icon40x40)); +#else + Style->Set("Updater.QuickLaunch", new IMAGE_CORE_BRUSH("Launcher/Launcher_Launch", Updater_Icon40x40)); +#endif + + 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/Payments"); + Style->Set( + "Updater.WechatPay", + new FSlateImageBrush( FPaths::Combine(PluginResourceDir , TEXT("wechatpay.png")), + Updater_IconPay) + ); + Style->Set( + "Updater.AliPay", + new FSlateImageBrush( FPaths::Combine(PluginResourceDir , TEXT("alipay.png")), + Updater_IconPay) + ); + return Style; +} + +const FSlateBrush* FVersionUpdaterStyle::GetBrush( FName PropertyName, const ANSICHAR* Specifier) +{ + return FVersionUpdaterStyle::StyleInstance->GetBrush( PropertyName, Specifier ); +} + +#undef IMAGE_BRUSH +#undef BOX_BRUSH +#undef BORDER_BRUSH +#undef TTF_FONT +#undef OTF_FONT + +void FVersionUpdaterStyle::ReloadTextures() +{ + if (FSlateApplication::IsInitialized()) + { + FSlateApplication::Get().GetRenderer()->ReloadTextureResources(); + } +} + +const ISlateStyle& FVersionUpdaterStyle::Get() +{ + return *StyleInstance; +} diff --git a/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/VersionUpdaterStyle.h b/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/VersionUpdaterStyle.h new file mode 100644 index 0000000..f93a65b --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Private/SVersionUpdater/VersionUpdaterStyle.h @@ -0,0 +1,32 @@ +// Copyright 2019 Lipeng Zha, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Styling/SlateStyle.h" + +class FVersionUpdaterStyle +{ +public: + + static void Initialize(const FString& Name); + + static void Shutdown(); + + /** reloads textures used by slate renderer */ + static void ReloadTextures(); + + /** @return The Slate style set for the Shooter game */ + static const ISlateStyle& Get(); + + static FName GetStyleSetName(); + + static const FSlateBrush* GetBrush( FName PropertyName, const ANSICHAR* Specifier = NULL ); +private: + + static TSharedRef< class FSlateStyleSet > Create(const FString& Name); + +private: + + static TSharedPtr< class FSlateStyleSet > StyleInstance; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherEditor/Public/Cooker/SHotPatcherCookerBase.h b/HotPatcher/Source/HotPatcherEditor/Public/Cooker/SHotPatcherCookerBase.h new file mode 100644 index 0000000..c35ce49 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Public/Cooker/SHotPatcherCookerBase.h @@ -0,0 +1,47 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "Interfaces/ITargetPlatformManagerModule.h" +#include "Interfaces/ITargetPlatform.h" +#include "CreatePatch/FExportPatchSettings.h" +#include "CreatePatch/IPatchableInterface.h" +#include "Model/FCookersModeContext.h" +// engine header +#include "Cooker/HotPatcherCookerSettingBase.h" +#include "Interfaces/ITargetPlatform.h" +#include "Templates/SharedPointer.h" +#include "IDetailsView.h" +#include "MissionNotificationProxy.h" +#include "PropertyEditorModule.h" +#include "SHotPatcherWidgetBase.h" +#include "ThreadUtils/FProcWorkerThread.hpp" +#include "Widgets/Text/SMultiLineEditableText.h" +/** + * Implements the cooked platforms panel. + */ +class SHotPatcherCookerBase : public SHotPatcherWidgetInterface +{ +public: + + SLATE_BEGIN_ARGS(SHotPatcherCookerBase) { } + SLATE_END_ARGS() + +public: + + /** + * Constructs the widget. + * + * @param InArgs The Slate argument list. + */ + FORCEINLINE void Construct( const FArguments& InArgs,TSharedPtr InCreateModel) + { + mCreatePatchModel = InCreateModel; + } + + +protected: + FCookersModeContext* GetCookerModelPtr()const { return (FCookersModeContext*)mCreatePatchModel.Get(); } + TSharedPtr mCreatePatchModel; + +}; + diff --git a/HotPatcher/Source/HotPatcherEditor/Public/CreatePatch/SHotPatcherInformations.h b/HotPatcher/Source/HotPatcherEditor/Public/CreatePatch/SHotPatcherInformations.h new file mode 100644 index 0000000..62ce987 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Public/CreatePatch/SHotPatcherInformations.h @@ -0,0 +1,38 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "Templates/SharedPointer.h" +#include "IDetailsView.h" +#include "PropertyEditorModule.h" +#include "Widgets/Text/SMultiLineEditableText.h" + + +class SHotPatcherInformations + : public SCompoundWidget +{ +public: + + SLATE_BEGIN_ARGS(SHotPatcherInformations) { } + SLATE_END_ARGS() + +public: + + /** + * Constructs the widget. + * + * @param InArgs The Slate argument list. + */ + void Construct( const FArguments& InArgs); + +public: + + void SetExpanded(bool InExpand); + + void SetContent(const FString& InContent); + +private: + + TSharedPtr DiffAreaWidget; + TSharedPtr MulitText; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherEditor/Public/CreatePatch/SHotPatcherPatchWidget.h b/HotPatcher/Source/HotPatcherEditor/Public/CreatePatch/SHotPatcherPatchWidget.h new file mode 100644 index 0000000..592c84d --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Public/CreatePatch/SHotPatcherPatchWidget.h @@ -0,0 +1,89 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Model/FPatchersModeContext.h" +#include "CreatePatch/FExportPatchSettings.h" +#include "SHotPatcherInformations.h" +#include "SHotPatcherWidgetBase.h" +#include "FPatchVersionDiff.h" +#include "CreatePatch/FExportPatchSettings.h" + +// engine header +#include "Interfaces/ITargetPlatformManagerModule.h" +#include "Interfaces/ITargetPlatform.h" +#include "Templates/SharedPointer.h" +#include "IDetailsView.h" +#include "PropertyEditorModule.h" +#include "Widgets/Text/SMultiLineEditableText.h" +#include "IStructureDetailsView.h" + +/** + * Implements the cooked platforms panel. + */ +class SHotPatcherPatchWidget + : public SHotPatcherWidgetBase +{ +public: + + SLATE_BEGIN_ARGS(SHotPatcherPatchWidget) { } + SLATE_END_ARGS() + +public: + + /** + * Constructs the widget. + * + * @param InArgs The Slate argument list. + */ + void Construct( const FArguments& InArgs,TSharedPtr InCreateModel); + +// IPatchableInterface +public: + virtual void ImportConfig(); + virtual void ExportConfig()const; + virtual void ResetConfig(); + virtual void DoGenerate(); + virtual FString GetMissionName() override{return TEXT("Patch");} +protected: + void CreateExportFilterListView(); + bool CanExportPatch()const; + FReply DoExportPatch(); + virtual FText GetGenerateTooltipText() const override; + + bool CanPreviewPatch()const; + FReply DoPreviewPatch(); + + FReply DoAddToPreset()const; + FReply DoDiff()const; + bool CanDiff()const; + FReply DoClearDiff()const; + EVisibility VisibilityDiffButtons()const; + + FReply DoPreviewChunk()const; + bool CanPreviewChunk()const; + EVisibility VisibilityPreviewChunkButtons()const; + + bool InformationContentIsVisibility()const; + void SetInformationContent(const FString& InContent)const; + void SetInfomationContentVisibility(EVisibility InVisibility)const; + + virtual FExportPatchSettings* GetConfigSettings() override{return ExportPatchSetting.Get();}; + + virtual void ImportProjectConfig() override; +protected: + + void ShowMsg(const FString& InMsg)const; + +private: + + // TSharedPtr mCreatePatchModel; + + /** Settings view ui element ptr */ + TSharedPtr SettingsView; + + TSharedPtr ExportPatchSetting; + + TSharedPtr DiffWidget; +}; + diff --git a/HotPatcher/Source/HotPatcherEditor/Public/CreatePatch/SHotPatcherReleaseWidget.h b/HotPatcher/Source/HotPatcherEditor/Public/CreatePatch/SHotPatcherReleaseWidget.h new file mode 100644 index 0000000..e67152c --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Public/CreatePatch/SHotPatcherReleaseWidget.h @@ -0,0 +1,58 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Model/FPatchersModeContext.h" +#include "SHotPatcherWidgetBase.h" +#include "CreatePatch/FExportPatchSettings.h" +#include "CreatePatch/FExportReleaseSettings.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 SHotPatcherReleaseWidget + : public SHotPatcherWidgetBase +{ +public: + + SLATE_BEGIN_ARGS(SHotPatcherReleaseWidget) { } + 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 ExportConfig()const; + virtual void ResetConfig(); + virtual void DoGenerate(); + virtual FString GetMissionName() override{return TEXT("Release");} + virtual FExportReleaseSettings* GetConfigSettings()override{return ExportReleaseSettings.Get();}; +protected: + void CreateExportFilterListView(); + bool CanExportRelease()const; + FReply DoExportRelease(); + virtual FText GetGenerateTooltipText() const override; +private: + + // TSharedPtr mCreatePatchModel; + + /** Settings view ui element ptr */ + TSharedPtr SettingsView; + TSharedPtr ExportReleaseSettings; +}; + diff --git a/HotPatcher/Source/HotPatcherEditor/Public/CreatePatch/SPatchersPage.h b/HotPatcher/Source/HotPatcherEditor/Public/CreatePatch/SPatchersPage.h new file mode 100644 index 0000000..05cd5d3 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Public/CreatePatch/SPatchersPage.h @@ -0,0 +1,45 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" +#include "Model/FPatchersModeContext.h" +#include "SHotPatcherWidgetBase.h" +#include "SHotPatcherPageBase.h" +// engine header +#include "Interfaces/ITargetPlatform.h" +#include "Templates/SharedPointer.h" +#include "IDetailsView.h" +#include "PropertyEditorModule.h" + +/** + * Implements the profile page for the session launcher wizard. + */ +class SPatchersPage + : public SHotPatcherPageBase +{ +public: + + SLATE_BEGIN_ARGS(SPatchersPage) { } + SLATE_END_ARGS() + +public: + + /** + * Constructs the widget. + * + * @param InArgs The Slate argument list. + */ + void Construct( const FArguments& InArgs,TSharedPtr InContext); + +public: + FText HandlePatchModeComboButtonContentText() const; + void HandleHotPatcherMenuEntryClicked(FString InModeName,TFunction ActionCallback); + virtual FString GetPageName()const override { return TEXT("Patcher"); }; + EVisibility HandleOperatorConfigVisibility()const; + EVisibility HandleImportProjectConfigVisibility()const; + + +}; diff --git a/HotPatcher/Source/HotPatcherEditor/Public/FlibHotPatcherEditorHelper.h b/HotPatcher/Source/HotPatcherEditor/Public/FlibHotPatcherEditorHelper.h new file mode 100644 index 0000000..3ff9727 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Public/FlibHotPatcherEditorHelper.h @@ -0,0 +1,22 @@ +#pragma once + +// 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" +#include "FlibHotPatcherEditorHelper.generated.h" + +UCLASS() +class HOTPATCHEREDITOR_API UFlibHotPatcherEditorHelper : public UBlueprintFunctionLibrary +{ + 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/HotPatcherEditor/Public/HotPatcherActionManager.h b/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherActionManager.h new file mode 100644 index 0000000..e7254ce --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherActionManager.h @@ -0,0 +1,124 @@ +#pragma once +// engine header +#include "CoreMinimal.h" +#include "SHotPatcherWidgetBase.h" +#include "Widgets/SCompoundWidget.h" +#include "Model/FHotPatcherContextBase.h" +#include "Kismet/KismetTextLibrary.h" + +using FRequestWidgetPtr = TFunction(TSharedPtr)>; + +struct HOTPATCHEREDITOR_API FHotPatcherActionDesc +{ + 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 +{ + FHotPatcherAction()=default; + FHotPatcherAction( + FName InCategory, + FName InModName, + const TAttribute& InActionName, + const TAttribute& InActionToolTip, + const FSlateIcon& InIcon, + TFunction InCallback, + FRequestWidgetPtr InRequestWidget, + int32 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; + TFunction ActionCallback; + FRequestWidgetPtr RequestWidget; + int32 Priority = 0; +}; + +struct HOTPATCHEREDITOR_API FHotPatcherModDesc +{ + FString ModName; + bool bIsBuiltInMod = false; + float CurrentVersion = 0.f; + FString Description; + FString URL; + FString UpdateURL; + float MinHotPatcherVersion = 0.0f; + TArray ModActions; +}; + +struct FHotPatcherCategory +{ + FHotPatcherCategory()=default; + FHotPatcherCategory(const FHotPatcherCategory&)=default; + FHotPatcherCategory(const FString InCategoryName,TSharedRef InWidget):CategoryName(InCategoryName),Widget(InWidget){} + + FString CategoryName; + TSharedRef Widget; +}; + +DECLARE_MULTICAST_DELEGATE_ThreeParams(FHotPatcherActionDelegate,const FString&,const FString&,const FHotPatcherAction&); + +struct HOTPATCHEREDITOR_API FHotPatcherActionManager +{ + static FHotPatcherActionManager Manager; + static FHotPatcherActionManager& Get() + { + return Manager; + } + virtual void Init(); + virtual void Shutdown(); + + void RegisterCategory(const FHotPatcherCategory& Category); + + 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); + + float GetHotPatcherVersion()const; + + FHotPatcherActionDelegate OnHotPatcherActionRegisted; + FHotPatcherActionDelegate OnHotPatcherActionUnRegisted; + + typedef TMap> FHotPatcherActionsType; + typedef TMap FHotPatcherCategoryType; + + FHotPatcherCategoryType& GetHotPatcherCategorys(){ return HotPatcherCategorys; } + 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; + TMap UnSupportModsDesc; +}; + + diff --git a/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherCommands.h b/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherCommands.h new file mode 100644 index 0000000..c1574b5 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherCommands.h @@ -0,0 +1,27 @@ +// Copyright 2019 Lipeng Zha, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Framework/Commands/Commands.h" +#include "HotPatcherStyle.h" + +class FHotPatcherCommands : public TCommands +{ +public: + + FHotPatcherCommands() + : TCommands(TEXT("HotPatcher"), NSLOCTEXT("Contexts", "HotPatcher", "HotPatcher Plugin"), NAME_None, FHotPatcherStyle::GetStyleSetName()) + { + } + + // TCommands<> interface + virtual void RegisterCommands() override; + +public: + TSharedPtr< FUICommandInfo > PluginAction; + + TSharedPtr CookSelectedAction; + TSharedPtr CookAndPakSelectedAction; + TSharedPtr AddToPakSettingsAction; +}; diff --git a/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherEditor.h b/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherEditor.h new file mode 100644 index 0000000..0df4526 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherEditor.h @@ -0,0 +1,108 @@ +// Copyright 2019 Lipeng Zha, Inc. All Rights Reserved. + +#pragma once + +#if WITH_EDITOR_SECTION + #include "ToolMenuContext.h" + #include "ToolMenu.h" +#endif + +#include "HotPatcherSettings.h" +#include "ETargetPlatform.h" +#include "FlibHotPatcherCoreHelper.h" +#include "HotPatcherSettings.h" +#include "CreatePatch/FExportPatchSettings.h" +#include "CreatePatch/FExportReleaseSettings.h" +#include "Model/FHotPatcherContextBase.h" +#include "HotPatcherActionManager.h" +#include "HotPatcherCore.h" + +// engine header +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" +#include "ContentBrowserDelegates.h" +#include "MissionNotificationProxy.h" +#include "ThreadUtils/FProcWorkerThread.hpp" + +#if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION>=26 + #define InvokeTab TryInvokeTab +#endif + +DECLARE_LOG_CATEGORY_EXTERN(LogHotPatcherEdotor,All,All) + +class FToolBarBuilder; +class FMenuBuilder; + + +struct FContentBrowserSelectedInfo +{ + TArray SelectedAssets; + TArray SelectedPaths; + TArray OutAllAssetPackages; +}; + +class HOTPATCHEREDITOR_API FHotPatcherEditorModule : public IModuleInterface +{ +public: + static FHotPatcherEditorModule& Get(); + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + void OpenDockTab(); + /** This function will be bound to Command. */ + void PluginButtonClicked(); + +public: + void AddToolbarExtension(FToolBarBuilder& Builder); + void AddMenuExtension(FMenuBuilder& Builder); + + TSharedPtr CreateProcMissionThread(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 + void CreateRootMenu(); + void CreateAssetContextMenu(FToolMenuSection& InSection); + void ExtendContentBrowserAssetSelectionMenu(); + void ExtendContentBrowserPathSelectionMenu(); + void MakeCookActionsSubMenu(UToolMenu* Menu); + void MakeCookAndPakActionsSubMenu(UToolMenu* Menu); + void MakeHotPatcherPresetsActionsSubMenu(UToolMenu* Menu); + void OnAddToPatchSettings(const FToolMenuContext& MenuContent); +#endif + 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); + void CookAndPakByPatchSettings(TSharedPtr PatchSettings,bool bForceStandalone); + void OnPakPreset(FExportPatchSettings Config,ETargetPlatform Platform); + void OnPakPreset(FExportPatchSettings Config); + void OnObjectSaved( UObject* ObjectSaved ); + + + FExportPatchSettings MakeTempPatchSettings( + const FString& Name, + const TArray& AssetIncludeFilters, + const TArray& IncludeSpecifyAssets, + const TArray& ExternFiles, + const TArray& PakTargetPlatforms, + bool bCook + ); +private: + TArray GetAllCookPlatforms()const; + TSharedRef OnSpawnPluginTab(const class FSpawnTabArgs& InSpawnTabArgs); + void OnTabClosed(TSharedRef InTab); + TArray GetSelectedAssetsInBrowserContent(); + TArray GetSelectedFolderInBrowserContent(); + +private: + TSharedPtr PluginCommands; + TSharedPtr DockTab; + mutable TSharedPtr mProcWorkingThread; + UMissionNotificationProxy* MissionNotifyProay = NULL; + TSharedPtr PatchSettings; + TArray Proxys; + FString StyleSetName; +}; diff --git a/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherModBaseModule.h b/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherModBaseModule.h new file mode 100644 index 0000000..cb93a2d --- /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 FHotPatcherModDesc GetModDesc()const { return ModDesc; }; +private: + FHotPatcherModDesc ModDesc; +}; diff --git a/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherStyle.h b/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherStyle.h new file mode 100644 index 0000000..9ef4234 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Public/HotPatcherStyle.h @@ -0,0 +1,31 @@ +// Copyright 2019 Lipeng Zha, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Styling/SlateStyle.h" + +class FHotPatcherStyle +{ +public: + + static void Initialize(); + + static void Shutdown(); + + /** reloads textures used by slate renderer */ + static void ReloadTextures(); + + /** @return The Slate style set for the Shooter game */ + static const ISlateStyle& Get(); + + static FName GetStyleSetName(); + +private: + + static TSharedRef< class FSlateStyleSet > Create(); + +private: + + static TSharedPtr< class FSlateStyleSet > StyleInstance; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherEditor/Public/MissionNotificationProxy.h b/HotPatcher/Source/HotPatcherEditor/Public/MissionNotificationProxy.h new file mode 100644 index 0000000..69627c5 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Public/MissionNotificationProxy.h @@ -0,0 +1,38 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" +#include "MissionNotificationProxy.generated.h" + +class FProcWorkerThread; + +DECLARE_MULTICAST_DELEGATE(FMissionCanceled); +/** + * + */ +UCLASS() +class HOTPATCHEREDITOR_API UMissionNotificationProxy : public UObject +{ + GENERATED_UCLASS_BODY() +public: + + virtual void ReceiveOutputMsg(FProcWorkerThread* Worker,const FString& InMsg); + virtual void SpawnRuningMissionNotification(FProcWorkerThread* ProcWorker); + virtual void SpawnMissionSuccessedNotification(FProcWorkerThread* ProcWorker); + virtual void SpawnMissionFaildNotification(FProcWorkerThread* ProcWorker); + virtual void CancelMission(); + + virtual void SetMissionName(FName NewMissionName); + virtual void SetMissionNotifyText(const FText& RunningText,const FText& CancelText,const FText& SuccessedText,const FText& FaildText); + FMissionCanceled MissionCanceled; +protected: + TWeakPtr PendingProgressPtr; + bool bRunning = false; + FText RunningNotifyText; + FText RunningNofityCancelText; + FText MissionSuccessedNotifyText; + FText MissionFailedNotifyText; + FName MissionName; +}; diff --git a/HotPatcher/Source/HotPatcherEditor/Public/Model/FCookersModeContext.h b/HotPatcher/Source/HotPatcherEditor/Public/Model/FCookersModeContext.h new file mode 100644 index 0000000..9c41f4a --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Public/Model/FCookersModeContext.h @@ -0,0 +1,31 @@ +#pragma once + +#include "CoreMinimal.h" +#include "FHotPatcherContextBase.h" +#include "Templates/HotPatcherTemplateHelper.hpp" +#include "FCookersModeContext.generated.h" + +UENUM(BlueprintType) +enum class EHotPatcherCookActionMode:uint8 +{ + ByOriginal, + Count UMETA(Hidden) +}; +ENUM_RANGE_BY_COUNT(EHotPatcherCookActionMode, EHotPatcherCookActionMode::Count); + +struct HOTPATCHEREDITOR_API FCookersModeContext:public FHotPatcherContextBase +{ +public: + virtual FName GetContextName()const override{ return TEXT("Cooker"); } +}; + +#if ENGINE_MAJOR_VERSION <= 4 && ENGINE_MINOR_VERSION <= 21 +namespace THotPatcherTemplateHelper +{ + template<> + std::string GetCPPTypeName() + { + return std::string("EHotPatcherCookActionMode"); + }; +} +#endif \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherEditor/Public/Model/FHotPatcherContextBase.h b/HotPatcher/Source/HotPatcherEditor/Public/Model/FHotPatcherContextBase.h new file mode 100644 index 0000000..b740adf --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Public/Model/FHotPatcherContextBase.h @@ -0,0 +1,26 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Templates/HotPatcherTemplateHelper.hpp" + +#define CREATE_ACTION_WIDGET_LAMBDA(WIDGET_CLASS,MODENAME) \ + [](TSharedPtr InContext)->TSharedRef{\ + return SNew(WIDGET_CLASS,InContext).Visibility_Lambda([=]()->EVisibility{\ + return InContext->GetModeName().IsEqual(MODENAME) ? EVisibility::Visible : EVisibility::Collapsed;\ + });} + +struct HOTPATCHEREDITOR_API FHotPatcherContextBase +{ +public: + FHotPatcherContextBase()=default; + FHotPatcherContextBase(const FHotPatcherContextBase&)=default; + virtual ~FHotPatcherContextBase(){} + virtual FName GetContextName()const{ return TEXT(""); } + FORCEINLINE_DEBUGGABLE virtual void SetModeByName(FName InPatcherModeName) + { + ModeName = InPatcherModeName.ToString(); + } + FORCEINLINE_DEBUGGABLE virtual FName GetModeName(){ return *ModeName; } +protected: + FString ModeName; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherEditor/Public/Model/FOriginalCookerContext.h b/HotPatcher/Source/HotPatcherEditor/Public/Model/FOriginalCookerContext.h new file mode 100644 index 0000000..952783a --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Public/Model/FOriginalCookerContext.h @@ -0,0 +1,223 @@ +#pragma once + +#include "FlibAssetManageHelper.h" +#include "FCookerConfig.h" +#include "FlibPatchParserHelper.h" +#include "FlibHotPatcherCoreHelper.h" +#include "FHotPatcherContextBase.h" + +// engine header +#include "Misc/App.h" +#include "CoreMinimal.h" + + +#include "Engine/Engine.h" +#include "Kismet/KismetSystemLibrary.h" +#include "Delegates/DelegateCombinations.h" + +DECLARE_DELEGATE_OneParam(FRequestExSettingsDlg, TArray&); +DECLARE_DELEGATE_OneParam(FRequestSpecifyCookFilterDlg, TArray&); + +struct HOTPATCHEREDITOR_API FOriginalCookerContext: public FHotPatcherContextBase +{ +public: + FOriginalCookerContext()=default; + virtual FName GetContextName()const override{ return TEXT("OriginalCooker"); } + virtual ~FOriginalCookerContext()override{} + void AddSelectedCookPlatform(const FString& InPlatfotm) + { + if (!mSelectedPlatform.Contains(InPlatfotm)) + { + mSelectedPlatform.Add(InPlatfotm); + } + } + + void RemoveSelectedCookPlatform(const FString& InPlatfotm) + { + if (mSelectedPlatform.Contains(InPlatfotm)) + { + mSelectedPlatform.Remove(InPlatfotm); + } + } + + const TArray& GetAllSelectedPlatform() + { + return mSelectedPlatform; + } + void ClearAllPlatform() + { + if(mSelectedPlatform.Num()>0) + mSelectedPlatform.Reset(); + } + + void AddSelectedCookMap(const FString& InNewMap) + { + if (!mSelectedCookMaps.Contains(InNewMap)) + { + mSelectedCookMaps.AddUnique(InNewMap); + } + } + void RemoveSelectedCookMap(const FString& InMap) + { + if (mSelectedCookMaps.Contains(InMap)) + { + mSelectedCookMaps.Remove(InMap); + } + } + const TArray& GetAllSelectedCookMap() + { + return mSelectedCookMaps; + } + void ClearAllMap() + { + if (mSelectedCookMaps.Num() > 0) + mSelectedCookMaps.Reset(); + } + + void AddSelectedSetting(const FString& InSetting) + { + if (!mOptionSettings.Contains(InSetting)) + { + mOptionSettings.AddUnique(InSetting); + } + } + void RemoveSelectedSetting(const FString& InSetting) + { + if (mOptionSettings.Contains(InSetting)) + { + mOptionSettings.Remove(InSetting); + } + } + + void ClearAllSettings() + { + if (mOptionSettings.Num() > 0) + mOptionSettings.Reset(); + } + const TArray& GetAllSelectedSettings() + { + return mOptionSettings; + } + + TArray GetAlwayCookFilters() + { + TArray CookFilters; + OnRequestSpecifyCookFilter.ExecuteIfBound(CookFilters); + return CookFilters; + } + + TArray GetAlwayCookFiltersFullPath() + { + TArray result; + for (const auto& CookFilter : GetAlwayCookFilters()) + { + FString FilterFullPath; + if (UFlibAssetManageHelper::ConvRelativeDirToAbsDir(CookFilter.Path, FilterFullPath)) + { + if (FPaths::DirectoryExists(FilterFullPath)) + { + result.AddUnique(FilterFullPath); + } + } + } + return result; + + } + + bool CombineAllCookParams(FString& OutCommand,FString& OutFaildReson) + { + FString result; + if (!(mSelectedPlatform.Num() > 0)) + { + OutFaildReson = TEXT("Not Selected any Platform."); + return false; + } + if (!(mSelectedCookMaps.Num() > 0 || GetAlwayCookFilters().Num() > 0)&& !mOptionSettings.Contains(TEXT("CookAll"))) + { + OutFaildReson = TEXT("Not Selected any Cookable things."); + return false; + } + result.Append(TEXT("-targetplatform=")); + for (int32 Index = 0; Index < mSelectedPlatform.Num(); ++Index) + { + result.Append(mSelectedPlatform[Index]); + if (!(Index == mSelectedPlatform.Num() - 1)) + { + result.Append(TEXT("+")); + } + } + result.Append(TEXT(" ")); + + if (GetAllSelectedCookMap().Num() > 0) + { + result.Append(TEXT("-Map=")); + for (int32 Index = 0; Index < mSelectedCookMaps.Num(); ++Index) + { + result.Append(mSelectedCookMaps[Index]); + if (!(Index == mSelectedCookMaps.Num() - 1)) + { + result.Append(TEXT("+")); + } + } + result.Append(TEXT(" ")); + } + + for (const auto& Option : mOptionSettings) + { + result.Append(TEXT("-") + Option + TEXT(" ")); + } + + // request cook directory + for (const auto& CookFilterFullPath : GetAlwayCookFiltersFullPath()) + { + result.Append(FString::Printf(TEXT("-COOKDIR=\"%s\" "), *CookFilterFullPath)); + } + + // Request Ex Options + for (const auto& ExOption : GetCookExSetting()) + { + result.Append(ExOption); + } + + OutCommand = result; + return true; + } + + TArray GetCookExSetting() + { + TArray ExOptions; + OnRequestExSettings.ExecuteIfBound(ExOptions); + return ExOptions; + } + + FCookerConfig GetCookConfig() + { + FCookerConfig result; + FString ProjectFilePath = UFlibPatchParserHelper::GetProjectFilePath(); + FString EngineBin = UFlibHotPatcherCoreHelper::GetUECmdBinary(); + + result.EngineBin = EngineBin; + result.ProjectPath = ProjectFilePath; + result.EngineParams = TEXT("-run=cook"); + + result.CookPlatforms = mSelectedPlatform; + result.CookMaps = mSelectedCookMaps; + TArray AllGameMap = UFlibPatchParserHelper::GetAvailableMaps(UKismetSystemLibrary::GetProjectDirectory(), ENABLE_COOK_ENGINE_MAP, ENABLE_COOK_PLUGIN_MAP, true); + result.bCookAllMap = result.CookMaps.Num() == AllGameMap.Num(); + for (const auto& Filter : GetAlwayCookFilters()) + { + result.CookFilter.AddUnique(Filter.Path); + } + result.CookSettings = mOptionSettings; + result.Options = GetCookExSetting()[0]; + + return result; + } +public: + FRequestExSettingsDlg OnRequestExSettings; + FRequestSpecifyCookFilterDlg OnRequestSpecifyCookFilter; +private: + TArray mSelectedPlatform; + TArray mSelectedCookMaps; + TArray mOptionSettings; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherEditor/Public/Model/FPatchersModeContext.h b/HotPatcher/Source/HotPatcherEditor/Public/Model/FPatchersModeContext.h new file mode 100644 index 0000000..86d7eb5 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Public/Model/FPatchersModeContext.h @@ -0,0 +1,35 @@ +#pragma once +#include "Templates/HotPatcherTemplateHelper.hpp" +#include "CoreMinimal.h" +#include "FHotPatcherContextBase.h" +#include "FPatchersModeContext.generated.h" + +UENUM(BlueprintType) +enum class EHotPatcherActionModes:uint8 +{ + ByPatch, + ByRelease, + ByShaderPatch, + ByGameFeature, + Count UMETA(Hidden) +}; +ENUM_RANGE_BY_COUNT(EHotPatcherActionModes, EHotPatcherActionModes::Count); + + +struct HOTPATCHEREDITOR_API FPatchersModeContext: public FHotPatcherContextBase +{ +public: + virtual ~FPatchersModeContext(){}; + virtual FName GetContextName()const override{ return TEXT("Patch"); } +}; + +#if ENGINE_MAJOR_VERSION <= 4 && ENGINE_MINOR_VERSION <= 21 +namespace THotPatcherTemplateHelper +{ + template<> + std::string GetCPPTypeName() + { + return std::string("EHotPatcherActionModes"); + }; +} +#endif \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherEditor/Public/SHotPatcherPageBase.h b/HotPatcher/Source/HotPatcherEditor/Public/SHotPatcherPageBase.h new file mode 100644 index 0000000..d71fe88 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Public/SHotPatcherPageBase.h @@ -0,0 +1,57 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" +#include "Model/FPatchersModeContext.h" +#include "SHotPatcherWidgetBase.h" + +// engine header +#include "Interfaces/ITargetPlatform.h" +#include "Templates/SharedPointer.h" +#include "IDetailsView.h" +#include "PropertyEditorModule.h" + +/** + * Implements the profile page for the session launcher wizard. + */ +class SHotPatcherPageBase + : public SCompoundWidget +{ +public: + + SLATE_BEGIN_ARGS(SHotPatcherPageBase) { } + SLATE_END_ARGS() + +public: + + /** + * Constructs the widget. + * + * @param InArgs The Slate argument list. + */ + void Construct( const FArguments& InArgs,TSharedPtr InContext) + { + SetContext(InContext); + } + virtual void SetContext(TSharedPtr InContext) + { + Context = InContext; + } + + FReply DoExportConfig()const; + FReply DoImportConfig()const; + FReply DoImportProjectConfig()const; + FReply DoResetConfig()const; + + virtual TSharedPtr GetContext()const { return Context; } + virtual TMap> GetActionWidgetMap()const { return ActionWidgetMap; } + virtual FString GetPageName()const { return TEXT(""); }; + virtual TSharedPtr GetActiveAction()const; + +protected: + TSharedPtr Context; + mutable TMap> ActionWidgetMap; +}; diff --git a/HotPatcher/Source/HotPatcherEditor/Public/SHotPatcherWidgetBase.h b/HotPatcher/Source/HotPatcherEditor/Public/SHotPatcherWidgetBase.h new file mode 100644 index 0000000..4a4c490 --- /dev/null +++ b/HotPatcher/Source/HotPatcherEditor/Public/SHotPatcherWidgetBase.h @@ -0,0 +1,72 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "Interfaces/ITargetPlatformManagerModule.h" +#include "Interfaces/ITargetPlatform.h" +#include "Model/FPatchersModeContext.h" +#include "CreatePatch/FExportPatchSettings.h" +#include "CreatePatch/IPatchableInterface.h" +#include "Model/FHotPatcherContextBase.h" + +// engine header +#include "Interfaces/ITargetPlatform.h" +#include "Templates/SharedPointer.h" +#include "IDetailsView.h" +#include "MissionNotificationProxy.h" +#include "PropertyEditorModule.h" +#include "ThreadUtils/FProcWorkerThread.hpp" +#include "Widgets/Text/SMultiLineEditableText.h" + +class HOTPATCHEREDITOR_API SHotPatcherWidgetInterface + : public SCompoundWidget, public IPatchableInterface +{ +public: + + SLATE_BEGIN_ARGS(SHotPatcherWidgetInterface) { } + SLATE_END_ARGS() + + virtual void ImportProjectConfig(){}; + FORCEINLINE virtual void ImportConfig() {}; + FORCEINLINE virtual void ExportConfig()const {}; + FORCEINLINE virtual void ResetConfig() {}; + FORCEINLINE virtual void DoGenerate() {}; + virtual FPatcherEntitySettingBase* GetConfigSettings(){return nullptr;}; + virtual FString GetMissionName(){return TEXT("");}; + + virtual FText GetGenerateTooltipText() const; + virtual TArray OpenFileDialog()const; + virtual TArray SaveFileDialog()const; +}; +/** + * Implements the cooked platforms panel. + */ +class HOTPATCHEREDITOR_API SHotPatcherWidgetBase + : public SHotPatcherWidgetInterface +{ +public: + + SLATE_BEGIN_ARGS(SHotPatcherWidgetBase) { } + SLATE_END_ARGS() + +public: + + /** + * Constructs the widget. + * + * @param InArgs The Slate argument list. + */ + void Construct( const FArguments& InArgs,TSharedPtr InContext); + + virtual void ImportProjectConfig() override; + + virtual FHotPatcherSettingBase* GetConfigSettings(){return nullptr;}; + virtual TSharedPtr GetContext() { return mContext; } + virtual void SetContext(TSharedPtr InContext) + { + mContext = InContext; + } +protected: + TSharedPtr mContext; + +}; + diff --git a/HotPatcher/Source/HotPatcherRuntime/HotPatcherRuntime.Build.cs b/HotPatcher/Source/HotPatcherRuntime/HotPatcherRuntime.Build.cs new file mode 100644 index 0000000..628eaa3 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/HotPatcherRuntime.Build.cs @@ -0,0 +1,104 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using System; +using System.IO; +using UnrealBuildTool; + +public class HotPatcherRuntime : ModuleRules +{ + public HotPatcherRuntime(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + Path.Combine(EngineDirectory,"Source/Runtime/Launch"), + Path.Combine(ModuleDirectory,"Public"), + Path.Combine(ModuleDirectory,"Public/BaseTypes"), + Path.Combine(ModuleDirectory,"Public/Templates") + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + if (Target.bBuildEditor) + { + PublicDependencyModuleNames.AddRange(new string[] + { + "TargetPlatform" + }); + } + + PublicDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "RHI", + "Core", + "Projects", + "Json", + "JsonUtilities", + "PakFile", + "AssetRegistry", + "BinariesPatchFeature" + // ... add other public dependencies that you statically link with here ... + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + "HTTP", + "Sockets" + // ... 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"); + } + + BuildVersion Version; + BuildVersion.TryRead(BuildVersion.GetDefaultFileName(), out Version); + // PackageContext + System.Func AddPublicDefinitions = (string MacroName,bool bEnable) => + { + PublicDefinitions.Add(string.Format("{0}={1}",MacroName, bEnable ? 1 : 0)); + return true; + }; + + AddPublicDefinitions("WITH_EDITOR_SECTION", Version.MajorVersion > 4 || Version.MinorVersion > 24); + bool bForceSingleThread = (Version.MajorVersion == 4 && Version.MinorVersion < 25) || + (Version.MajorVersion == 5 && Version.MinorVersion >= 1); + AddPublicDefinitions("FORCE_SINGLE_THREAD",bForceSingleThread); + + bool bEnableAssetDependenciesDebugLog = true; + AddPublicDefinitions("ASSET_DEPENDENCIES_DEBUG_LOG", bEnableAssetDependenciesDebugLog); + + bool bCustomAssetGUID = false; + + if (Version.MajorVersion > 4) { bCustomAssetGUID = true; } + if(bCustomAssetGUID) + { + PublicDefinitions.Add("CUSTOM_ASSET_GUID"); + } + + bLegacyPublicIncludePaths = false; + OptimizeCode = CodeOptimization.InShippingBuildsOnly; + } +} diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/BaseTypes/AssetManager/FAssetDependenciesInfo.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/BaseTypes/AssetManager/FAssetDependenciesInfo.cpp new file mode 100644 index 0000000..2ff9914 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Private/BaseTypes/AssetManager/FAssetDependenciesInfo.cpp @@ -0,0 +1,118 @@ +#include "AssetManager/FAssetDependenciesInfo.h" +#include "FlibAssetManageHelper.h" +#include "HotPatcherRuntime.h" +#include "Async/ParallelFor.h" + + +void FAssetDependenciesInfo::AddAssetsDetail(const FAssetDetail& AssetDetail) +{ + if(!AssetDetail.IsValid()) + return; + FString CurrAssetModuleName = UFlibAssetManageHelper::GetAssetBelongModuleName(AssetDetail.PackagePath.ToString()); + FSoftObjectPath CurrAssetObjectPath(AssetDetail.PackagePath.ToString()); + FString CurrAssetLongPackageName = CurrAssetObjectPath.GetLongPackageName(); + if (!AssetsDependenciesMap.Contains(CurrAssetModuleName)) + { + FAssetDependenciesDetail AssetDependenciesDetail{ CurrAssetModuleName,TMap{ {CurrAssetLongPackageName,AssetDetail} } }; + AssetsDependenciesMap.Add(CurrAssetModuleName, AssetDependenciesDetail); + } + else + { + FAssetDependenciesDetail& CurrentCategory = *AssetsDependenciesMap.Find(CurrAssetModuleName); + + if (!CurrentCategory.AssetDependencyDetails.Contains(CurrAssetLongPackageName)) + { + CurrentCategory.AssetDependencyDetails.Add(CurrAssetLongPackageName,AssetDetail); + } + } +} + +bool FAssetDependenciesInfo::HasAsset(const FString& InAssetPackageName)const +{ + SCOPED_NAMED_EVENT_TEXT("IsHasAsset",FColor::Red); + bool bHas = false; + FString BelongModuleName = UFlibAssetManageHelper::GetAssetBelongModuleName(InAssetPackageName); + if (AssetsDependenciesMap.Contains(BelongModuleName)) + { + bHas = AssetsDependenciesMap.Find(BelongModuleName)->AssetDependencyDetails.Contains(InAssetPackageName); + } + return bHas; +} + +TArray FAssetDependenciesInfo::GetAssetDetails()const +{ + SCOPED_NAMED_EVENT_TEXT("FAssetDependenciesInfo::GetAssetDetails",FColor::Red); + + TArray AssetDetails; + TArray Keys; + AssetsDependenciesMap.GetKeys(Keys); + + FCriticalSection SynchronizationObject; + ParallelFor(Keys.Num(),[&](int32 index) + { + const TMap& ModuleAssetDetails = AssetsDependenciesMap.Find(Keys[index])->AssetDependencyDetails; + + TArray ModuleAssetKeys; + ModuleAssetDetails.GetKeys(ModuleAssetKeys); + + for (const auto& ModuleAssetKey : ModuleAssetKeys) + { + FScopeLock Lock(&SynchronizationObject); + AssetDetails.Add(*ModuleAssetDetails.Find(ModuleAssetKey)); + } + },GForceSingleThread); + + return AssetDetails; +} + +bool FAssetDependenciesInfo::GetAssetDetailByPackageName(const FString& InAssetPackageName,FAssetDetail& OutDetail) const +{ + bool bHas = HasAsset(InAssetPackageName); + if(bHas) + { + FString BelongModuleName = UFlibAssetManageHelper::GetAssetBelongModuleName(InAssetPackageName); + OutDetail = *AssetsDependenciesMap.Find(BelongModuleName)->AssetDependencyDetails.Find(InAssetPackageName); + } + return bHas; +} + +TArray FAssetDependenciesInfo::GetAssetLongPackageNames()const +{ + SCOPED_NAMED_EVENT_TEXT("FAssetDependenciesInfo::GetAssetLongPackageNames",FColor::Red); + TArray OutAssetLongPackageName; + const TArray& OutAssetDetails = GetAssetDetails(); + + for (const auto& Asset : OutAssetDetails) + { + FString LongPackageName = UFlibAssetManageHelper::PackagePathToLongPackageName(Asset.PackagePath.ToString()); + { + OutAssetLongPackageName.AddUnique(LongPackageName); + } + } + return OutAssetLongPackageName; +} + +void FAssetDependenciesInfo::RemoveAssetDetail(const FAssetDetail& AssetDetail) +{ + SCOPED_NAMED_EVENT_TEXT("FAssetDependenciesInfo::RemoveAssetDetail",FColor::Red); + FString LongPackageName = UFlibAssetManageHelper::PackagePathToLongPackageName(AssetDetail.PackagePath.ToString()); + RemoveAssetDetail(LongPackageName); +} + +void FAssetDependenciesInfo::RemoveAssetDetail(const FString& LongPackageName) +{ + FString BelongModuleName = UFlibAssetManageHelper::GetAssetBelongModuleName(LongPackageName); + if (AssetsDependenciesMap.Contains(BelongModuleName)) + { + TMap& AssetDependencyDetails = AssetsDependenciesMap.Find(BelongModuleName)->AssetDependencyDetails; + bool bHas = AssetDependencyDetails.Contains(LongPackageName); + if(bHas) + { + AssetDependencyDetails.Remove(LongPackageName); + if(!AssetDependencyDetails.Num()) + { + AssetsDependenciesMap.Remove(BelongModuleName); + } + } + } +} diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/BaseTypes/FAssetScanConfig.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/BaseTypes/FAssetScanConfig.cpp new file mode 100644 index 0000000..d91f664 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Private/BaseTypes/FAssetScanConfig.cpp @@ -0,0 +1,38 @@ +#include "BaseTypes/FAssetScanConfig.h" + +bool FAssetScanConfig::IsMatchForceSkip(const FSoftObjectPath& ObjectPath,FString& OutReason) +{ + SCOPED_NAMED_EVENT_TEXT("IsMatchForceSkip",FColor::Red); + bool bSkip = false; + if(bForceSkipContent) + { + bool bSkipAsset = ForceSkipAssets.Contains(ObjectPath); + if(bSkipAsset) + { + OutReason = FString::Printf(TEXT("IsForceSkipAsset")); + } + bool bSkipDir = false; + for(const auto& ForceSkipDir:ForceSkipContentRules) + { + if(ObjectPath.GetLongPackageName().StartsWith(ForceSkipDir.Path)) + { + bSkipDir = true; + OutReason = FString::Printf(TEXT("ForceSkipDir %s"),*ForceSkipDir.Path); + break; + } + } + bool bSkipClasses = false; + FName AssetClassesName = UFlibAssetManageHelper::GetAssetType(ObjectPath); + for(const auto& Classes:ForceSkipClasses) + { + if(Classes->GetFName().IsEqual(AssetClassesName)) + { + OutReason = FString::Printf(TEXT("ForceSkipClasses %s"),*Classes->GetName()); + bSkipClasses = true; + break; + } + } + bSkip = bSkipAsset || bSkipDir || bSkipClasses; + } + return bSkip; +} diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/BaseTypes/FBinariesPatchConfig.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/BaseTypes/FBinariesPatchConfig.cpp new file mode 100644 index 0000000..7326e4f --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Private/BaseTypes/FBinariesPatchConfig.cpp @@ -0,0 +1,106 @@ +#include "FBinariesPatchConfig.h" +#include "FlibPatchParserHelper.h" + +FString FBinariesPatchConfig::GetBinariesPatchFeatureName() const +{ + return THotPatcherTemplateHelper::GetEnumNameByValue(BinariesPatchType); +} + +FString FBinariesPatchConfig::GetOldCookedDir() const +{ + return UFlibPatchParserHelper::ReplaceMarkPath(OldCookedDir.Path); +} + +FString FBinariesPatchConfig::GetBasePakExtractCryptoJson() const +{ + FString JsonFile; + if(EncryptSettings.bUseDefaultCryptoIni) + { + JsonFile = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectSavedDir(),TEXT("HotPatcher/Crypto.json"))); + FPakEncryptionKeys PakEncryptionKeys = UFlibPatchParserHelper::GetCryptoByProjectSettings(); + UFlibPatchParserHelper::SerializePakEncryptionKeyToFile(PakEncryptionKeys,JsonFile); + } + else + { + JsonFile = UFlibPatchParserHelper::ReplaceMarkPath(EncryptSettings.CryptoKeys.FilePath); + } + return JsonFile; +} + +bool FBinariesPatchConfig::IsMatchIgnoreRules(const FPakCommandItem& File) +{ + bool bIsIgnore = false; + if(!FPaths::FileExists(File.AssetAbsPath)) + return false; + uint64 FileSize = IFileManager::Get().FileSize(*File.AssetAbsPath); + + auto FormatMatcher = [](const FString& File,const TArray& Formaters)->bool + { + bool bFormatMatch = !Formaters.Num(); + for(const auto& FileFormat:Formaters) + { + if(!FileFormat.IsEmpty() && File.EndsWith(FileFormat,ESearchCase::CaseSensitive)) + { + bFormatMatch = true; + break; + } + } + return bFormatMatch; + }; + + auto SizeMatcher = [](uint64 FileSize,const FMatchRule& MatchRule)->bool + { + bool bMatch = false; + uint64 RuleSize = MatchRule.Size * 1024; // kb to byte + switch (MatchRule.Operator) + { + case EMatchOperator::GREAT_THAN: + { + bMatch = FileSize > RuleSize; + break; + } + case EMatchOperator::LESS_THAN: + { + bMatch = FileSize < RuleSize; + break; + } + case EMatchOperator::EQUAL: + { + bMatch = FileSize == RuleSize; + break; + } + case EMatchOperator::None: + { + bMatch = true; + break; + } + } + return bMatch; + }; + + for(const auto& Rule:GetMatchRules()) + { + bool bSizeMatch = SizeMatcher(FileSize,Rule); + bool bFormatMatch = FormatMatcher(File.AssetAbsPath,Rule.Formaters); + bool bMatched = bSizeMatch && bFormatMatch; + bIsIgnore = Rule.Rule == EMatchRule::MATCH ? !bMatched : bMatched; + } + return bIsIgnore; +} + + +TArray FBinariesPatchConfig::GetBaseVersionPakByPlatform(ETargetPlatform Platform) +{ + TArray result; + for(const auto& BaseVersionPak:GetBaseVersionPaks()) + { + if(BaseVersionPak.Platform == Platform) + { + for(const auto& Path:BaseVersionPak.Paks) + { + result.Emplace(UFlibPatchParserHelper::ReplaceMarkPath(Path.FilePath)); + } + } + } + return result; +} \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/BaseTypes/FChunkInfo.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/BaseTypes/FChunkInfo.cpp new file mode 100644 index 0000000..d9ebb07 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Private/BaseTypes/FChunkInfo.cpp @@ -0,0 +1,169 @@ +#include "BaseTypes/FChunkInfo.h" + +#include "FlibPatchParserHelper.h" +#include "Engine/AssetManager.h" + + +FString FChunkInfo::GetShaderLibraryName() const +{ + FString ShaderLibraryName; + switch (GetCookShaderOptions().ShaderNameRule) + { + case EShaderLibNameRule::CHUNK_NAME: + { + ShaderLibraryName = ChunkName; + break; + } + case EShaderLibNameRule::PROJECT_NAME: + { + ShaderLibraryName = FApp::GetProjectName(); + break; + } + case EShaderLibNameRule::CUSTOM: + { + ShaderLibraryName = GetCookShaderOptions().CustomShaderName; + break; + } + } + return ShaderLibraryName; +} + + +TArray FChunkInfo::GetManagedAssets() const +{ + FScopedNamedEventStatic GetManagedAssetsTag(FColor::Red,*FString::Printf(TEXT("GetManagedAssets_%s"),*ChunkName)); + TArray NewPaths; + + UAssetManager& Manager = UAssetManager::Get(); + IAssetRegistry& AssetRegistry = Manager.GetAssetRegistry(); + + FHotPatcherVersion ChunkManageAssetsVersion = UFlibPatchParserHelper::ExportReleaseVersionInfoByChunk( + TEXT(""), + TEXT(""), + TEXT(""), + *this, + false, + bAnalysisFilterDependencies + ); + const TArray& LabelManagedAssets = ChunkManageAssetsVersion.AssetInfo.GetAssetDetails(); + if (LabelManagedAssets.Num()) + { + for (const auto& AssetDetail : LabelManagedAssets) + { + FAssetData AssetData; + bool bAssetDataStatus = UFlibAssetManageHelper::GetAssetsDataByPackageName(AssetDetail.PackagePath.ToString(),AssetData); + if(bAssetDataStatus && AssetData.IsValid()) + { + FSoftObjectPath AssetRef = Manager.GetAssetPathForData(AssetData); + if (!AssetRef.IsNull()) + { + NewPaths.Add(AssetRef); + } + } + } + } + return NewPaths; +} + + + +bool FChunkAssetDescribe::HasValidAssets()const +{ + bool bHasValidExFiles = false; + for(const auto& Item:AllPlatformExFiles) + { + if(!!Item.Value.ExternFiles.Num()) + { + bHasValidExFiles = true; + break; + } + } + + return !!GetAssetsDetail().Num() || InternalFiles.HasValidAssets() || bHasValidExFiles; +} + +TArray FChunkAssetDescribe::GetAssetsDetail()const +{ + return Assets.GetAssetDetails(); +} + + +TArray FChunkAssetDescribe::GetAssetsStrings()const +{ + TArray AllUnselectedAssets; + const TArray& OutAssetDetails = GetAssetsDetail(); + + for (const auto& AssetDetail : OutAssetDetails) + { + AllUnselectedAssets.AddUnique(AssetDetail.PackagePath); + } + return AllUnselectedAssets; +} + +TArray FChunkAssetDescribe::GetExFilesByPlatform(ETargetPlatform Platform)const +{ + TArray result; + if(AllPlatformExFiles.Contains(Platform)) + { + result = AllPlatformExFiles.Find(Platform)->ExternFiles; + } + return result; +} +TArray FChunkAssetDescribe::GetExternalFileNames(ETargetPlatform Platform)const +{ + TArray ExFilesResult; + auto CollectExFilesStrings = [](const TArray& InFiles)->TArray + { + TArray result; + for (const auto& File : InFiles) + { + result.AddUnique(FName(*File.FilePath.FilePath)); + } + return result; + }; + if(AllPlatformExFiles.Contains(Platform)) + { + ExFilesResult = CollectExFilesStrings(GetExFilesByPlatform(Platform)); + } + return ExFilesResult; +} + + +TArray FChunkAssetDescribe::GetInternalFileNames()const +{ + TArray result; + { + if (InternalFiles.bIncludeAssetRegistry) { result.Add(TEXT("bIncludeAssetRegistry")); }; + if (InternalFiles.bIncludeGlobalShaderCache) { result.Add(TEXT("bIncludeGlobalShaderCache")); }; + if (InternalFiles.bIncludeShaderBytecode) { result.Add(TEXT("bIncludeShaderBytecode")); }; + if (InternalFiles.bIncludeEngineIni) { result.Add(TEXT("bIncludeEngineIni")); }; + if (InternalFiles.bIncludePluginIni) { result.Add(TEXT("bIncludePluginIni")); }; + if (InternalFiles.bIncludeProjectIni) { result.Add(TEXT("bIncludeProjectIni")); }; + } + return result; +} + +FChunkInfo FChunkAssetDescribe::AsChunkInfo(const FString& ChunkName) +{ + FChunkInfo DefaultChunk; + + DefaultChunk.ChunkName = ChunkName; + DefaultChunk.bMonolithic = false; + DefaultChunk.InternalFiles = GetInternalInfo(); + DefaultChunk.bOutputDebugInfo = false; + DefaultChunk.bStorageUnrealPakList = true; + DefaultChunk.bStorageIoStorePakList = true; + for(const auto& AssetDetail:GetAssetsDetail()) + { + FPatcherSpecifyAsset Asset; + Asset.Asset.SetPath(AssetDetail.PackagePath.ToString()); + DefaultChunk.IncludeSpecifyAssets.AddUnique(Asset); + } + for(const auto& ExFiles:AllPlatformExFiles) + { + FPlatformExternAssets PlatformFiles; + PlatformFiles.TargetPlatform = ExFiles.Key; + PlatformFiles.AddExternFileToPak = ExFiles.Value.ExternFiles; + } + return DefaultChunk; +} \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/BaseTypes/FExternFileInfo.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/BaseTypes/FExternFileInfo.cpp new file mode 100644 index 0000000..39d48fa --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Private/BaseTypes/FExternFileInfo.cpp @@ -0,0 +1,14 @@ +#include "BaseTypes/FExternFileInfo.h" + +#include "FlibPatchParserHelper.h" + +FString FExternFileInfo::GenerateFileHash(EHashCalculator HashCalculator) +{ + FileHash = GetFileHash(HashCalculator); + return FileHash; +} + +FString FExternFileInfo::GetFileHash(EHashCalculator HashCalculator)const +{ + return UFlibPatchParserHelper::FileHash(FilePath.FilePath,HashCalculator); +} \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/FExportPatchSettings.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/FExportPatchSettings.cpp new file mode 100644 index 0000000..c3e71d1 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/FExportPatchSettings.cpp @@ -0,0 +1,180 @@ +#include "CreatePatch/FExportPatchSettings.h" +#include "FlibAssetManageHelper.h" +#include "HotPatcherLog.h" +#include "FlibPatchParserHelper.h" +#include "Engine/World.h" + +FCookAdvancedOptions::FCookAdvancedOptions() +{ + OverrideNumberOfAssetsPerFrame.Add(UWorld::StaticClass(),2); +} + +FExportPatchSettings::FExportPatchSettings() + :bEnableExternFilesDiff(true), + DefaultPakListOptions{ TEXT("-compress") }, + DefaultCommandletOptions{ TEXT("-compress") ,TEXT("-compressionformats=Zlib")} +{ + // IoStoreSettings.IoStoreCommandletOptions.Add(TEXT("-CreateGlobalContainer=")); + PakVersionFileMountPoint = FPaths::Combine( + TEXT("../../../"), + UFlibPatchParserHelper::GetProjectName(), + TEXT("Versions/version.json") + ); +} + +void FExportPatchSettings::Init() +{ + Super::Init(); +} + +FString FExportPatchSettings::GetBaseVersion() const +{ + return UFlibPatchParserHelper::ReplaceMarkPath(BaseVersion.FilePath); +} + +FPakVersion FExportPatchSettings::GetPakVersion(const FHotPatcherVersion& InHotPatcherVersion, const FString& InUtcTime) +{ + FPakVersion PakVersion; + PakVersion.BaseVersionId = InHotPatcherVersion.BaseVersionId; + PakVersion.VersionId = InHotPatcherVersion.VersionId; + PakVersion.Date = InUtcTime; + + // encode BaseVersionId_VersionId_Data to SHA1 + PakVersion.CheckCode = UFlibPatchParserHelper::HashStringWithSHA1( + FString::Printf( + TEXT("%s_%s_%s"), + *PakVersion.BaseVersionId, + *PakVersion.VersionId, + *PakVersion.Date + ) + ); + + return PakVersion; +} + +FString FExportPatchSettings::GetSavePakVersionPath(const FString& InSaveAbsPath, const FHotPatcherVersion& InVersion) +{ + FString PatchSavePath(InSaveAbsPath); + FPaths::MakeStandardFilename(PatchSavePath); + + FString SavePakVersionFilePath = FPaths::Combine( + PatchSavePath, + FString::Printf( + TEXT("%s_PakVersion.json"), + *InVersion.VersionId + ) + ); + return SavePakVersionFilePath; +} + +FString FExportPatchSettings::GetPakCommandsSaveToPath(const FString& InSaveAbsPath,const FString& InPlatfornName, const FHotPatcherVersion& InVersion) +{ + FString SavePakCommandPath = FPaths::Combine( + InSaveAbsPath, + InPlatfornName, + !InVersion.BaseVersionId.IsEmpty()? + FString::Printf(TEXT("PakList_%s_%s_%s_PakCommands.txt"), *InVersion.BaseVersionId, *InVersion.VersionId, *InPlatfornName): + FString::Printf(TEXT("PakList_%s_%s_PakCommands.txt"), *InVersion.VersionId, *InPlatfornName) + ); + + return SavePakCommandPath; +} + +FHotPatcherVersion FExportPatchSettings::GetNewPatchVersionInfo() +{ + FHotPatcherVersion BaseVersionInfo; + GetBaseVersionInfo(BaseVersionInfo); + + FHotPatcherVersion CurrentVersion; + CurrentVersion.VersionId = GetVersionId(); + CurrentVersion.BaseVersionId = BaseVersionInfo.VersionId; + CurrentVersion.Date = FDateTime::UtcNow().ToString(); + UFlibPatchParserHelper::RunAssetScanner(GetAssetScanConfig(),CurrentVersion); + UFlibPatchParserHelper::ExportExternAssetsToPlatform(GetAddExternAssetsToPlatform(),CurrentVersion,false,GetHashCalculator()); + + // UFlibPatchParserHelper::ExportReleaseVersionInfo( + // GetVersionId(), + // BaseVersionInfo.VersionId, + // FDateTime::UtcNow().ToString(), + // UFlibAssetManageHelper::DirectoryPathsToStrings(GetAssetIncludeFilters()), + // UFlibAssetManageHelper::DirectoryPathsToStrings(GetAssetIgnoreFilters()), + // GetAllSkipContents(), + // GetForceSkipClasses(), + // GetAssetRegistryDependencyTypes(), + // GetIncludeSpecifyAssets(), + // GetAddExternAssetsToPlatform(), + // IsIncludeHasRefAssetsOnly() + // ); + + return CurrentVersion; +} + +bool FExportPatchSettings::GetBaseVersionInfo(FHotPatcherVersion& OutBaseVersion) const +{ + FString BaseVersionContent; + + bool bDeserializeStatus = false; + if (IsByBaseVersion()) + { + if (UFlibAssetManageHelper::LoadFileToString(GetBaseVersion(), BaseVersionContent)) + { + bDeserializeStatus = THotPatcherTemplateHelper::TDeserializeJsonStringAsStruct(BaseVersionContent, OutBaseVersion); + } + } + + return bDeserializeStatus; +} + +FString FExportPatchSettings::GetChunkSavedDir(const FString& InVersionId,const FString& InBaseVersionId,const FString& InChunkName,const FString& InPlatformName)const +{ + FReplacePakRegular TmpPakPathRegular{ + InVersionId, + InBaseVersionId, + InChunkName, + InPlatformName + }; + FString ReplacedPakPathRegular = UFlibPatchParserHelper::ReplacePakRegular(TmpPakPathRegular,GetPakPathRegular()); + return FPaths::Combine(GetSaveAbsPath(),ReplacedPakPathRegular); +} + +FString FExportPatchSettings::GetCurrentVersionSavePath() const +{ + FString CurrentVersionSavePath; + if(GetPakTargetPlatforms().Num()) + { + FString PlatformName = THotPatcherTemplateHelper::GetEnumNameByValue(GetPakTargetPlatforms()[0]); + FString SavedBaseDir = GetChunkSavedDir(GetVersionId(),TEXT(""),GetVersionId(),PlatformName); + CurrentVersionSavePath = FPaths::Combine(SavedBaseDir,TEXT("..")); + } + else + { + CurrentVersionSavePath = FPaths::Combine(GetSaveAbsPath(), /*const_cast(this)->GetNewPatchVersionInfo().*/VersionId); + } + FPaths::NormalizeFilename(CurrentVersionSavePath); + return CurrentVersionSavePath; +} + +FString FExportPatchSettings::GetCombinedAdditionalCommandletArgs() const +{ + return UFlibPatchParserHelper::MergeOptionsAsCmdline(TArray{ + FHotPatcherSettingBase::GetCombinedAdditionalCommandletArgs(), + UFlibPatchParserHelper::GetTargetPlatformsCmdLine(GetPakTargetPlatforms()), + IsEnableProfiling() ? TEXT("-trace=cpu,loadtimetrace") : TEXT("") + }); +} + +FString FExportPatchSettings::GetStorageCookedDir() const +{ + return UFlibPatchParserHelper::ReplaceMark(StorageCookedDir); +} + +TArray FExportPatchSettings::GetPakTargetPlatformNames() const +{ + TArray Resault; + for (const auto &Platform : GetPakTargetPlatforms()) + { + Resault.Add(THotPatcherTemplateHelper::GetEnumNameByValue(Platform)); + } + return Resault; +} + diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/FExportReleaseSettings.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/FExportReleaseSettings.cpp new file mode 100644 index 0000000..241e978 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/FExportReleaseSettings.cpp @@ -0,0 +1,167 @@ +#include "CreatePatch/FExportReleaseSettings.h" +#include "Kismet/KismetSystemLibrary.h" +#include "ReleaseParser/FReleasePakParser.h" +#include "ReleaseParser/FReleasePaklistParser.h" + +FExportReleaseSettings::FExportReleaseSettings(){} +FExportReleaseSettings::~FExportReleaseSettings(){}; + +void FExportReleaseSettings::Init() +{ + Super::Init(); +} + void FExportReleaseSettings::ImportPakLists() + { + UE_LOG(LogHotPatcher,Log,TEXT("FExportReleaseSettings::ImportPakList")); + + if(!GetPlatformsPakListFiles().Num()) + { + return; + } + TArray PlatformAssets; + for(const auto& PlatformPakList:GetPlatformsPakListFiles()) + { + TSharedPtr PakListConf = MakeShareable(new FReleasePakListConf); + PakListConf->TargetPlatform = PlatformPakList.TargetPlatform; + for(const auto& PakFile:PlatformPakList.PakResponseFiles) + { + PakListConf->PakResponseFiles.AddUnique(PakFile.FilePath); + } + if(!!PakListConf->PakResponseFiles.Num()) + { + FReleasePaklistParser PakListParser; + PakListParser.Parser(PakListConf,GetHashCalculator()); + PlatformAssets.Add(PakListParser.GetParserResult()); + } + + TSharedPtr PakFileConf = MakeShareable(new FReleasePakFilesConf); + PakFileConf->TargetPlatform = PlatformPakList.TargetPlatform; + for(const auto& PakFile:PlatformPakList.PakFiles) + { + PakFileConf->PakFiles.AddUnique(FPaths::ConvertRelativePathToFull(PakFile.FilePath)); + } + PakFileConf->AESKey = PlatformPakList.AESKey; + if(!!PakFileConf->PakFiles.Num()) + { + FReleasePakParser PakFileParser; + PakFileParser.Parser(PakFileConf,GetHashCalculator()); + PlatformAssets.Add(PakFileParser.GetParserResult()); + } + } + + PlatformAssets.Sort([](const FReleaseParserResult& l,const FReleaseParserResult& r)->bool{return l.Assets.Num()bool{return l.ExternFiles.Num() 1) + { + FReleaseParserResult AllPlatform; + AllPlatform.Platform = ETargetPlatform::AllPlatforms; + for(int32 FileIndex=0;FileIndexbool + { + bool result = false; + for(auto& ExistPlatform:AddExternAssetsToPlatform) + { + if(ExistPlatform.TargetPlatform == InPlatform) + { + result = true; + break; + } + } + return result; + }; + + if(CheckIsExisitPlatform(Platform.Platform)) + { + for(auto& ExistPlatform:AddExternAssetsToPlatform) + { + if(ExistPlatform.TargetPlatform == Platform.Platform) + { + ExistPlatform.AddExternFileToPak.Append(Platform.ExternFiles); + } + } + } + else + { + FPlatformExternAssets NewPlatform; + NewPlatform.TargetPlatform = Platform.Platform; + NewPlatform.AddExternFileToPak = Platform.ExternFiles; + AddExternAssetsToPlatform.Add(NewPlatform); + } + } + } + + +void FExportReleaseSettings::ClearImportedPakList() +{ + UE_LOG(LogHotPatcher,Log,TEXT("FExportReleaseSettings::ClearImportedPakList")); + AddExternAssetsToPlatform.Empty(); + GetAssetScanConfigRef().IncludeSpecifyAssets.Empty(); +} + +// for DetailView property change property +void FExportReleaseSettings::OnFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent) +{ + // PostEditChangeProperty(PropertyChangedEvent); +} + +void FExportReleaseSettings::PostEditChangeProperty(const FPropertyChangedEvent& PropertyChangedEvent) +{ + +} + +FExportReleaseSettings* FExportReleaseSettings::Get() +{ + static FExportReleaseSettings StaticIns; + return &StaticIns; +} + +FString FExportReleaseSettings::GetVersionId()const +{ + return VersionId; +} + diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/HotPatcherContext.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/HotPatcherContext.cpp new file mode 100644 index 0000000..e339254 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/HotPatcherContext.cpp @@ -0,0 +1,138 @@ +#include "CreatePatch/HotPatcherContext.h" + +void FPackageTrackerByDiff::OnPackageCreated(UPackage* Package) +{ + FString PackageName = Package->GetName(); + + // get asset detail by current version + FAssetDetail CurrentVersionAssetDetail = UFlibAssetManageHelper::GetAssetDetailByPackageName(PackageName); + + bool bNeedAdd = true; + + auto HasAssstInDependenciesInfo = [&CurrentVersionAssetDetail](const FAssetDependenciesInfo& DependenciesInfo,const FString& PackageName)->bool + { + bool bHasInDependenciesInfo = false; + if(DependenciesInfo.HasAsset(PackageName)) + { + FAssetDetail BaseVersionAssetDetail; + // get asset detail by base version + if(DependenciesInfo.GetAssetDetailByPackageName(PackageName,BaseVersionAssetDetail)) + { + if(BaseVersionAssetDetail == CurrentVersionAssetDetail) + { + bHasInDependenciesInfo = true; + } + } + } + return bHasInDependenciesInfo; + }; + if(HasAssstInDependenciesInfo(Context.BaseVersion.AssetInfo,PackageName) || + HasAssstInDependenciesInfo(Context.VersionDiff.AssetDiffInfo.AddAssetDependInfo,PackageName) || + HasAssstInDependenciesInfo(Context.VersionDiff.AssetDiffInfo.ModifyAssetDependInfo,PackageName) + ) + { + bNeedAdd = false; + } + + if(bNeedAdd) + { + TrackedAssets.Add(FName(*PackageName),CurrentVersionAssetDetail); + } +} + +FPatchVersionExternDiff* FHotPatcherPatchContext::GetPatcherDiffInfoByName(const FString& PlatformName) +{ + ETargetPlatform Platform; + THotPatcherTemplateHelper::GetEnumValueByName(PlatformName,Platform); + // add new file to diff + FPatchVersionExternDiff* PatchVersionExternDiff = NULL; + { + if(!VersionDiff.PlatformExternDiffInfo.Contains(Platform)) + { + FPatchVersionExternDiff AdditionalFiles; + VersionDiff.PlatformExternDiffInfo.Add(Platform,AdditionalFiles); + } + PatchVersionExternDiff = VersionDiff.PlatformExternDiffInfo.Find(Platform); + } + return PatchVersionExternDiff; +}; +FPlatformExternAssets* FHotPatcherPatchContext::GetPatcherChunkInfoByName(const FString& PlatformName,const FString& ChunkName) +{ + SCOPED_NAMED_EVENT_TEXT("FHotPatcherPatchContext::GetPatcherChunkInfoByName",FColor::Red); + ETargetPlatform Platform; + THotPatcherTemplateHelper::GetEnumValueByName(PlatformName,Platform); + FPlatformExternAssets* PlatformExternAssetsPtr = NULL; + { + auto PlatformExternAssetsPtrByChunk = [](FChunkInfo& ChunkInfo,ETargetPlatform Platform) + { + FPlatformExternAssets* PlatformExternAssetsPtr = NULL; + for(auto& PlatfromExternAssetsRef:ChunkInfo.AddExternAssetsToPlatform) + { + if(PlatfromExternAssetsRef.TargetPlatform == Platform) + { + PlatformExternAssetsPtr = &PlatfromExternAssetsRef; + } + } + if(!PlatformExternAssetsPtr) + { + FPlatformExternAssets PlatformExternAssets; + PlatformExternAssets.TargetPlatform = Platform; + int32 index = ChunkInfo.AddExternAssetsToPlatform.Add(PlatformExternAssets); + PlatformExternAssetsPtr = &ChunkInfo.AddExternAssetsToPlatform[index]; + } + return PlatformExternAssetsPtr; + }; + + for(auto& PakChunk:PakChunks) + { + if(PakChunk.ChunkName.Equals(ChunkName)) + { + PlatformExternAssetsPtr = PlatformExternAssetsPtrByChunk(PakChunk,Platform); + } + } + if(!PlatformExternAssetsPtr) + { + PlatformExternAssetsPtr = PlatformExternAssetsPtrByChunk(PakChunks[0],Platform); + } + return PlatformExternAssetsPtr; + } +} + +bool FHotPatcherPatchContext::AddAsset(const FString ChunkName, const FAssetDetail& AssetDetail) +{ + SCOPED_NAMED_EVENT_TEXT("FHotPatcherPatchContext::AddAsset",FColor::Red); + bool bRet = false; + + if(!AssetDetail.IsValid()) + return false; + + FString PackageName = UFlibAssetManageHelper::PackagePathToLongPackageName(AssetDetail.PackagePath.ToString()); + + bool bInBaseVersion = false; + // has base version + if(GetSettingObject()->bByBaseVersion) + { + bInBaseVersion = BaseVersion.AssetInfo.HasAsset(PackageName); + } + FAssetDependenciesInfo& AddtoDependencies = bInBaseVersion ? VersionDiff.AssetDiffInfo.ModifyAssetDependInfo : VersionDiff.AssetDiffInfo.AddAssetDependInfo; + + if(!AddtoDependencies.HasAsset(PackageName)) + { + UE_LOG(LogHotPatcher,Display,TEXT("Add %s to Chunk %s"),*AssetDetail.PackagePath.ToString(),*ChunkName); + AddtoDependencies.AddAssetsDetail(AssetDetail); + for(auto& ChunkInfo:PakChunks) + { + if(ChunkInfo.ChunkName.Equals(ChunkName)) + { + FPatcherSpecifyAsset CurrentAsset; + CurrentAsset.Asset = AssetDetail.PackagePath.ToString(); + CurrentAsset.bAnalysisAssetDependencies = false; + CurrentAsset.AssetRegistryDependencyTypes = {EAssetRegistryDependencyTypeEx::None}; + ChunkInfo.IncludeSpecifyAssets.AddUnique(CurrentAsset); + bRet = true; + } + } + } + return bRet; +} + \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/HotPatcherSettingBase.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/HotPatcherSettingBase.cpp new file mode 100644 index 0000000..598d4e1 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Private/CreatePatch/HotPatcherSettingBase.cpp @@ -0,0 +1,124 @@ +#include "CreatePatch/HotPatcherSettingBase.h" +#include "FlibPatchParserHelper.h" +#include "FPlatformExternFiles.h" +#include "HotPatcherLog.h" + +#include "Misc/EngineVersionComparison.h" + +FHotPatcherSettingBase::FHotPatcherSettingBase() +{ + GetAssetScanConfigRef().bAnalysisFilterDependencies = true; + GetAssetScanConfigRef().AssetRegistryDependencyTypes = TArray{EAssetRegistryDependencyTypeEx::Packages}; + GetAssetScanConfigRef().ForceSkipContentRules.Append(UFlibPatchParserHelper::GetDefaultForceSkipContentDir()); + SavePath.Path = TEXT("[PROJECTDIR]/Saved/HotPatcher/"); +} + + +TArray& FHotPatcherSettingBase::GetAddExternAssetsToPlatform() +{ + static TArray PlatformNoAssets; + return PlatformNoAssets; +}; + + +void FHotPatcherSettingBase::Init() +{ + +} + +TArray FHotPatcherSettingBase::GetAllExternFilesByPlatform(ETargetPlatform InTargetPlatform,bool InGeneratedHash) +{ + TArray AllExternFiles = UFlibPatchParserHelper::ParserExDirectoryAsExFiles(GetAddExternDirectoryByPlatform(InTargetPlatform)); + + for (auto& ExFile : GetAddExternFilesByPlatform(InTargetPlatform)) + { + if (!AllExternFiles.Contains(ExFile)) + { + AllExternFiles.Add(ExFile); + } + } + if (InGeneratedHash) + { + for (auto& ExFile : AllExternFiles) + { + ExFile.GenerateFileHash(GetHashCalculator()); + } + } + return AllExternFiles; +} + +TMap FHotPatcherSettingBase::GetAllPlatfotmExternFiles(bool InGeneratedHash) +{ + TMap result; + + for(const auto& Platform:GetAddExternAssetsToPlatform()) + { + FPlatformExternFiles PlatformIns{Platform.TargetPlatform,GetAllExternFilesByPlatform(Platform.TargetPlatform,InGeneratedHash)}; + result.Add(Platform.TargetPlatform,PlatformIns); + } + return result; +} + +TArray FHotPatcherSettingBase::GetAddExternFilesByPlatform(ETargetPlatform InTargetPlatform) +{ + TArray result; + for(const auto& Platform:GetAddExternAssetsToPlatform()) + { + if (Platform.TargetPlatform == InTargetPlatform) + { + for(const auto& File:Platform.AddExternFileToPak) + { + uint32 index = result.Emplace(File); + result[index].FilePath.FilePath = UFlibPatchParserHelper::ReplaceMarkPath(File.FilePath.FilePath); + } + } + } + + return result; +} +TArray FHotPatcherSettingBase::GetAddExternDirectoryByPlatform(ETargetPlatform InTargetPlatform) +{ + TArray result; + for(const auto& Platform:GetAddExternAssetsToPlatform()) + { + if (Platform.TargetPlatform == InTargetPlatform) + { + for(const auto& Dir:Platform.AddExternDirectoryToPak) + { + uint32 index = result.Emplace(Dir); + result[index].DirectoryPath.Path = UFlibPatchParserHelper::ReplaceMarkPath(Dir.DirectoryPath.Path); + } + } + } + + return result; +} + +FString FHotPatcherSettingBase::GetSaveAbsPath()const +{ + if (!SavePath.Path.IsEmpty()) + { + return UFlibPatchParserHelper::ReplaceMarkPath(GetSavePath()); + } + return TEXT(""); +} + +FString FHotPatcherSettingBase::GetCombinedAdditionalCommandletArgs() const +{ + FString Result; + 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())); + AllSkipContents.Append(UFlibAssetManageHelper::SoftObjectPathsToStrings(GetForceSkipAssets())); + return AllSkipContents; +} + diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/DependenciesParser/FDefaultAssetDependenciesParser.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/DependenciesParser/FDefaultAssetDependenciesParser.cpp new file mode 100644 index 0000000..468d8e8 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Private/DependenciesParser/FDefaultAssetDependenciesParser.cpp @@ -0,0 +1,338 @@ +#include "DependenciesParser/FDefaultAssetDependenciesParser.h" +#include "FlibAssetManageHelper.h" +#include "HotPatcherLog.h" +#include "HotPatcherRuntime.h" +#include "Async/ParallelFor.h" +#include "Engine/World.h" +#include "Engine/WorldComposition.h" +#include "Resources/Version.h" + +void FAssetDependenciesParser::Parse(const FAssetDependencies& InParseConfig) +{ + ParseConfig = InParseConfig; + + SCOPED_NAMED_EVENT_TEXT("FAssetDependenciesParser::Parse",FColor::Red); + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + TSet AssetPackageNames; + + // make /Game/XXX to /Game/XXX/ + TArray IngnoreFilters = UFlibAssetManageHelper::NormalizeContentDirs(InParseConfig.IgnoreFilters); + TArray IncludeFilters = UFlibAssetManageHelper::NormalizeContentDirs(InParseConfig.IncludeFilters); + + { + SCOPED_NAMED_EVENT_TEXT("Get AssetPackageNames by filter",FColor::Red); + TArray AssetDatas; + UFlibAssetManageHelper::GetAssetsData(IncludeFilters,AssetDatas,!GForceSingleThread); + + for(const auto& AssetData:AssetDatas) + { + if(!IsIgnoreAsset(AssetData)) + { + AssetPackageNames.Add(AssetData.PackageName); + } + } + } + + Results.Append(AssetPackageNames); + + if(InParseConfig.AnalysicFilterDependencies) + { + SCOPED_NAMED_EVENT_TEXT("Get dependencies for filters",FColor::Red); + for(FName PackageName:AssetPackageNames) + { + Results.Append( + FAssetDependenciesParser::GatherAssetDependicesInfoRecursively( + AssetRegistryModule, + PackageName, + ParseConfig.AssetRegistryDependencyTypes, + true, + IngnoreFilters, + InParseConfig.ForceSkipContents, + InParseConfig.ForceSkipPackageNames, + ParseConfig.IgnoreAseetTypes, + ScanedCaches + )); + } + } + + { + SCOPED_NAMED_EVENT_TEXT("Get dependencies for SpecifyAsset",FColor::Red); + for(const auto& SpecifyAsset:ParseConfig.InIncludeSpecifyAsset) + { + FString LongPackageName = SpecifyAsset.Asset.GetLongPackageName(); + if(!SpecifyAsset.Asset.IsValid()) + { + continue; + } + FAssetData AssetData; + if(!LongPackageName.IsEmpty() && UFlibAssetManageHelper::GetAssetsDataByPackageName(LongPackageName,AssetData)) + { + if(IsIgnoreAsset(AssetData)) + { + continue; + } + } + Results.Add(FName(*SpecifyAsset.Asset.GetLongPackageName())); + if(SpecifyAsset.bAnalysisAssetDependencies) + { + Results.Append( + FAssetDependenciesParser::GatherAssetDependicesInfoRecursively( + AssetRegistryModule, + FName(*LongPackageName), + SpecifyAsset.AssetRegistryDependencyTypes, + SpecifyAsset.bAnalysisAssetDependencies, + IngnoreFilters, + InParseConfig.ForceSkipContents, + InParseConfig.ForceSkipPackageNames, + ParseConfig.IgnoreAseetTypes, + ScanedCaches + )); + } + } + } + + Results.Remove(FName(NAME_None)); + + // FCriticalSection SynchronizationObject; + // TSet ForceSkipPackages; + // TArray ResultArray = Results.Array(); + // ParallelFor(ResultArray.Num(),[&](int32 index) + // { + // FString AssetPackageNameStr = ResultArray[index].ToString(); + // if(IsForceSkipAsset(AssetPackageNameStr,ParseConfig.IgnoreAseetTypes,ParseConfig.ForceSkipContents)) + // { + // FScopeLock Lock(&SynchronizationObject); + // ForceSkipPackages.Add(ResultArray[index]); + // } + // },GForceSingleThread); + // + // for(FName SkipPackage:ForceSkipPackages) + // { + // Results.Remove(SkipPackage); + // } +} + +bool IsValidPackageName(const FString& LongPackageName) +{ + SCOPED_NAMED_EVENT_TEXT("IsValidPackageName",FColor::Red); + bool bStatus = false; + if (!LongPackageName.IsEmpty() && !FPackageName::IsScriptPackage(LongPackageName) && !FPackageName::IsMemoryPackage(LongPackageName)) + { + bStatus = true; + } + return bStatus; +} + +bool FAssetDependenciesParser::IsIgnoreAsset(const FAssetData& AssetData) +{ + FString LongPackageName = AssetData.PackageName.ToString(); + bool bIsForceSkip = IsForceSkipAsset(LongPackageName,ParseConfig.IgnoreAseetTypes,ParseConfig.IgnoreFilters,ParseConfig.ForceSkipContents,ParseConfig.ForceSkipPackageNames,true); + auto HashPackageFlag = [](uint32 Flags,uint32 CheckFlag)->bool + { + return (Flags & CheckFlag) != 0; + }; + bool bIsEditorFlag = HashPackageFlag(AssetData.PackageFlags,PKG_EditorOnly); + + return bIsForceSkip || bIsEditorFlag; +} + +bool FAssetDependenciesParser::IsForceSkipAsset(const FString& LongPackageName, const TSet& IgnoreTypes, const TArray& IgnoreFilters, TArray ForceSkipFilters, const TArray& ForceSkipPackageNames, bool + bDispalyLog) +{ + SCOPED_NAMED_EVENT_TEXT("IsForceSkipAsset",FColor::Red); + bool bIsIgnore = false; + FString MatchIgnoreStr; + if(UFlibAssetManageHelper::UFlibAssetManageHelper::MatchIgnoreTypes(LongPackageName,IgnoreTypes,MatchIgnoreStr)) + { + bIsIgnore = true; + } + + if(!bIsIgnore && UFlibAssetManageHelper::MatchIgnoreFilters(LongPackageName,IgnoreFilters,MatchIgnoreStr)) + { + bIsIgnore = true; + } + + if(!bIsIgnore && UFlibAssetManageHelper::MatchIgnoreFilters(LongPackageName,ForceSkipFilters,MatchIgnoreStr)) + { + bIsIgnore = true; + } + + if(!bIsIgnore && ForceSkipPackageNames.Contains(LongPackageName)) + { + bIsIgnore = true; + } + + if(bIsIgnore && bDispalyLog) + { +#if ASSET_DEPENDENCIES_DEBUG_LOG + UE_LOG(LogHotPatcher,Log,TEXT("Force Skip %s (match ignore rule %s)"),*LongPackageName,*MatchIgnoreStr); +#endif + } + return bIsIgnore; +} + +TArray ParserSkipAssetByDependencies(const FAssetData& CurrentAssetData,const TArray& CurrentAssetDependencies) +{ + SCOPED_NAMED_EVENT_TEXT("ParserSkipAssetByDependencies",FColor::Red); + bool bHasAtlsGroup = false; + TArray TextureSrouceRefs; + if(UFlibAssetManageHelper::GetAssetDataClasses(CurrentAssetData).IsEqual(TEXT("PaperSprite"))) + { + for(const auto& DependItemName:CurrentAssetDependencies) + { + FAssetDetail AssetDetail = UFlibAssetManageHelper::GetAssetDetailByPackageName(DependItemName.ToString()); + if(AssetDetail.IsValid()) + { + if(AssetDetail.AssetType.IsEqual(TEXT("PaperSpriteAtlas"))) + { + bHasAtlsGroup = true; + } + if(AssetDetail.AssetType.IsEqual(TEXT("Texture")) || AssetDetail.AssetType.IsEqual(TEXT("Texture2D"))) + { + TextureSrouceRefs.Add(DependItemName); + } + } + } + } + return bHasAtlsGroup ? TextureSrouceRefs : TArray{}; +} + +TSet FAssetDependenciesParser::GatherAssetDependicesInfoRecursively( + FAssetRegistryModule& InAssetRegistryModule, + FName InLongPackageName, const TArray& InAssetDependencyTypes, + bool bRecursively, + const TArray& IgnoreDirectories, + const TArray& ForceSkipDirectories, + const TArray& ForceSkipPackageNames, + const TSet& IgnoreAssetTypes, + FScanedCachesType& InScanedCaches) +{ + static bool bVerboseLog = FParse::Param(FCommandLine::Get(), TEXT("VerboseLog")); + + TSet AssetDependencies; + SCOPED_NAMED_EVENT_TEXT("GatherAssetDependicesInfoRecursively",FColor::Red); + TArray TempForceSkipPackageNames = ForceSkipPackageNames; + TempForceSkipPackageNames.Add(InLongPackageName.ToString()); + + FAssetData CurrentAssetData; + UFlibAssetManageHelper::GetAssetsDataByPackageName(InLongPackageName.ToString(),CurrentAssetData); + + bool bGetDependenciesSuccess = false; + EAssetRegistryDependencyType::Type TotalType = EAssetRegistryDependencyType::None; + + for (const auto& DepType : InAssetDependencyTypes) + { + TotalType = UFlibAssetManageHelper::ConvAssetRegistryDependencyToInternal(DepType); + } + + TArray CurrentAssetDependencies; + + { + SCOPED_NAMED_EVENT_TEXT("GetDependencies",FColor::Red); + PRAGMA_DISABLE_DEPRECATION_WARNINGS + bGetDependenciesSuccess = InAssetRegistryModule.Get().GetDependencies(InLongPackageName, CurrentAssetDependencies, TotalType); + PRAGMA_ENABLE_DEPRECATION_WARNINGS + + for(const auto& SkipForDependencies:ParserSkipAssetByDependencies(CurrentAssetData,CurrentAssetDependencies)) + { + CurrentAssetDependencies.Remove(SkipForDependencies); + } + + // collect world composition tile packages to cook + if(ParseConfig.bSupportWorldComposition && CurrentAssetData.GetClass() == UWorld::StaticClass()) + { +#if WITH_EDITOR + FString DisplayStr = FString::Printf(TEXT("LoadWorld %s"),*CurrentAssetData.GetFullName()); + FScopedNamedEvent CacheClassEvent(FColor::Red,*DisplayStr); + UWorld* World = UWorld::FindWorldInPackage(CurrentAssetData.GetAsset()->GetOutermost()); + if (World) + { + if(World->WorldComposition) + { + SCOPED_NAMED_EVENT_TEXT("Collect WorldComposition Tiles",FColor::Red); + TArray PackageNames; + World->WorldComposition->CollectTilesToCook(PackageNames); + for(const auto& PackageName:PackageNames) + { +#if ASSET_DEPENDENCIES_DEBUG_LOG + UE_LOG(LogHotPatcher,Log,TEXT("Collecting WorldComposition Tile Package %s for %s"),*InLongPackageName.ToString(),*PackageName); +#endif + CurrentAssetDependencies.AddUnique(FName(*PackageName)); + } + } + + } +#endif + } + + if (!bGetDependenciesSuccess) + return AssetDependencies; + } + + { + SCOPED_NAMED_EVENT_TEXT("check valid package and ignore rule",FColor::Red); + + FCriticalSection SynchronizationObject; + ParallelFor(CurrentAssetDependencies.Num(),[&](int32 index) + { + auto LongPackageName = CurrentAssetDependencies[index]; + FString LongPackageNameStr = LongPackageName.ToString(); + + // ignore /Script/ and self + if(LongPackageName.IsNone() || !IsValidPackageName(LongPackageNameStr) || LongPackageName == InLongPackageName) + { + return; + } + // check is ignore directories or ingore types + { + SCOPED_NAMED_EVENT_TEXT("check ignore directories",FColor::Red); + if(!IsForceSkipAsset(LongPackageNameStr,IgnoreAssetTypes,IgnoreDirectories,ForceSkipDirectories,TempForceSkipPackageNames,true)) + { + FScopeLock Lock(&SynchronizationObject); + AssetDependencies.Add(LongPackageName); + } + } + },true); + } + + if(bRecursively) + { + TSet Dependencies; +#if ASSET_DEPENDENCIES_DEBUG_LOG + if(bVerboseLog) + { + UE_LOG(LogHotPatcher,Display,TEXT("AssetParser %s Dependencies: (%d)"),*InLongPackageName.ToString(),AssetDependencies.Num()); + for(const auto& AssetPackageName:AssetDependencies) + { + UE_LOG(LogHotPatcher,Display,TEXT("\t%s"),*AssetPackageName.ToString()); + } + } +#endif + for(const auto& AssetPackageName:AssetDependencies) + { + if(AssetPackageName.IsNone()) + continue; + if(TempForceSkipPackageNames.Contains(AssetPackageName.ToString())) + continue; + + if(!InScanedCaches.Contains(AssetPackageName)) + { + InScanedCaches.Add(AssetPackageName, + GatherAssetDependicesInfoRecursively( + InAssetRegistryModule, + AssetPackageName, + InAssetDependencyTypes, + true, + IgnoreDirectories, + ForceSkipDirectories, + TempForceSkipPackageNames, + IgnoreAssetTypes, + InScanedCaches + )); + } + Dependencies.Append(*InScanedCaches.Find(AssetPackageName)); + } + AssetDependencies.Append(Dependencies); + } + return AssetDependencies; +} diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/FlibAssetManageHelper.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/FlibAssetManageHelper.cpp new file mode 100644 index 0000000..9dd0611 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Private/FlibAssetManageHelper.cpp @@ -0,0 +1,1635 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "FlibAssetManageHelper.h" +#include "AssetManager/FAssetDependenciesInfo.h" +#include "AssetManager/FAssetDependenciesDetail.h" +#include "AssetManager/FFileArrayDirectoryVisitor.hpp" + +#include "AssetRegistryModule.h" +#include "ARFilter.h" +#include "Kismet/KismetStringLibrary.h" +#include "Kismet/KismetSystemLibrary.h" +#include "Json.h" +#include "Templates/SharedPointer.h" +#include "Interfaces/IPluginManager.h" +#include "Engine/AssetManager.h" +#include "AssetData.h" +#include "UObject/ConstructorHelpers.h" +#include "Resources/Version.h" +#include "HotPatcherLog.h" +#include "HotPatcherRuntime.h" +#include "Async/ParallelFor.h" +#include "Templates/HotPatcherTemplateHelper.hpp" +#include "UObject/MetaData.h" +#include "UObject/UObjectHash.h" +#include "Misc/EngineVersionComparison.h" + +bool UFlibAssetManageHelper::bIncludeOnlyOnDiskAssets = !GForceSingleThread; + +// PRAGMA_DISABLE_DEPRECATION_WARNINGS +FString UFlibAssetManageHelper::PackagePathToFilename(const FString& InPackagePath) +{ + FString ResultAbsPath; + FSoftObjectPath ObjectPath = InPackagePath; + FString AssetAbsNotPostfix = FPaths::ConvertRelativePathToFull(FPackageName::LongPackageNameToFilename(ObjectPath.GetLongPackageName())); + FString AssetName = ObjectPath.GetAssetName(); + FString SearchDir; + { + int32 FoundIndex; + AssetAbsNotPostfix.FindLastChar('/', FoundIndex); + if (FoundIndex != INDEX_NONE) + { + SearchDir = UKismetStringLibrary::GetSubstring(AssetAbsNotPostfix, 0, FoundIndex); + } + } + + TArray localFindFiles; + IFileManager::Get().FindFiles(localFindFiles, *SearchDir, nullptr); + + for (const auto& Item : localFindFiles) + { + if (Item.Contains(AssetName) && Item[AssetName.Len()] == '.') + { + ResultAbsPath = FPaths::Combine(SearchDir, Item); + break; + } + } + + return ResultAbsPath; +} + +FString UFlibAssetManageHelper::LongPackageNameToFilename(const FString& InLongPackageName) +{ + return UFlibAssetManageHelper::PackagePathToFilename(UFlibAssetManageHelper::LongPackageNameToPackagePath(InLongPackageName)); +} + + +bool UFlibAssetManageHelper::FilenameToPackagePath(const FString& InAbsPath, FString& OutPackagePath) +{ + bool runState = false; + FString LongPackageName; + runState = FPackageName::TryConvertFilenameToLongPackageName(InAbsPath, LongPackageName); + + if (runState) + { + OutPackagePath = UFlibAssetManageHelper::LongPackageNameToPackagePath(LongPackageName); + runState = runState && true; + } + + return runState; +} + +void UFlibAssetManageHelper::UpdateAssetMangerDatabase(bool bForceRefresh) +{ + SCOPED_NAMED_EVENT_TEXT("UpdateAssetMangerDatabase",FColor::Red); +#if WITH_EDITOR + UAssetManager& AssetManager = UAssetManager::Get(); + AssetManager.UpdateManagementDatabase(bForceRefresh); +#endif +} + + +bool UFlibAssetManageHelper::GetAssetPackageGUID(const FString& InPackageName, FName& OutGUID) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::GetAssetPackageGUID",FColor::Red); + bool bResult = false; + if (InPackageName.IsEmpty()) + return false; + +#ifndef CUSTOM_ASSET_GUID + const FAssetPackageData* AssetPackageData = UFlibAssetManageHelper::GetPackageDataByPackageName(InPackageName); + if (AssetPackageData != NULL) + { + PRAGMA_DISABLE_DEPRECATION_WARNINGS + const FGuid& AssetGuid = AssetPackageData->PackageGuid; + PRAGMA_ENABLE_DEPRECATION_WARNINGS + OutGUID = FName(*AssetGuid.ToString()); + bResult = true; + } +#else + FString FileName; + // FSoftObjectPath CurrentPackagePath = InPackagePath; + // UObject* Object = CurrentPackagePath.TryLoad(); + // UPackage* Package = NULL; + // if(Object) + // { + // Package = Object->GetPackage(); + // } + // if(Package) + // { + // FString LongPackageName = CurrentPackagePath.GetLongPackageName(); + // FString Extersion = Package->ContainsMap() ? FPackageName::GetMapPackageExtension() : FPackageName::GetAssetPackageExtension(); + // bool bCovStatus = FPackageName::TryConvertLongPackageNameToFilename(LongPackageName,FileName,Extersion); + // } + + FileName = UFlibAssetManageHelper::LongPackageNameToFilename(InPackageName); + + if(!FileName.IsEmpty() && FPaths::FileExists(FileName)) + { + FMD5Hash FileMD5Hash = FMD5Hash::HashFile(*FileName); + FString HashValue = LexToString(FileMD5Hash); + OutGUID = FName(HashValue); + bResult = true; + } +#endif + 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) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::CombineAssetDependencies",FColor::Red); + FAssetDependenciesInfo resault; + + auto CombineLambda = [&resault](const FAssetDependenciesInfo& InDependencies) + { + TArray Keys; + InDependencies.AssetsDependenciesMap.GetKeys(Keys); + for (const auto& Key : Keys) + { + if (!resault.AssetsDependenciesMap.Contains(Key)) + { + resault.AssetsDependenciesMap.Add(Key, *InDependencies.AssetsDependenciesMap.Find(Key)); + } + else + { + + { + TMap& ExistingAssetDetails = resault.AssetsDependenciesMap.Find(Key)->AssetDependencyDetails; + TArray ExistingAssetList; + ExistingAssetDetails.GetKeys(ExistingAssetList); + + const TMap& PaddingAssetDetails = InDependencies.AssetsDependenciesMap.Find(Key)->AssetDependencyDetails; + TArray PaddingAssetList; + PaddingAssetDetails.GetKeys(PaddingAssetList); + + for (const auto& PaddingDetailItem : PaddingAssetList) + { + if (!ExistingAssetDetails.Contains(PaddingDetailItem)) + { + ExistingAssetDetails.Add(PaddingDetailItem,*PaddingAssetDetails.Find(PaddingDetailItem)); + } + } + } + } + } + }; + + CombineLambda(A); + CombineLambda(B); + + return resault; +} + + +bool UFlibAssetManageHelper::GetAssetReferenceByLongPackageName(const FString& LongPackageName, + const TArray& SearchAssetDepTypes, TArray& OutRefAsset) +{ + bool bStatus = false; + { + TArray AssetIdentifier; + + FAssetIdentifier InAssetIdentifier; + InAssetIdentifier.PackageName = FName(*LongPackageName); + AssetIdentifier.Add(InAssetIdentifier); + + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + TArray ReferenceNames; + for (const FAssetIdentifier& AssetId : AssetIdentifier) + { + for (const auto& AssetDepType : SearchAssetDepTypes) + { + TArray CurrentTypeReferenceNames; + PRAGMA_DISABLE_DEPRECATION_WARNINGS + AssetRegistryModule.Get().GetReferencers(AssetId, CurrentTypeReferenceNames, AssetDepType); + PRAGMA_ENABLE_DEPRECATION_WARNINGS + for (const auto& Name : CurrentTypeReferenceNames) + { + if (!(Name.PackageName.ToString() == LongPackageName)) + { + ReferenceNames.AddUnique(Name); + } + } + } + + } + + for (const auto& RefAssetId : ReferenceNames) + { + FAssetDetail RefAssetDetail; + if (UFlibAssetManageHelper::GetSpecifyAssetDetail(RefAssetId.PackageName.ToString(), RefAssetDetail)) + { + OutRefAsset.Add(RefAssetDetail); + } + } + bStatus = true; + } + return bStatus; +} + + +bool UFlibAssetManageHelper::GetAssetReference(const FAssetDetail& InAsset, const TArray& SearchAssetDepTypes, TArray& OutRefAsset) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::GetAssetReference",FColor::Red); + FString LongPackageName = UFlibAssetManageHelper::PackagePathToLongPackageName(InAsset.PackagePath.ToString()); + return UFlibAssetManageHelper::GetAssetReferenceByLongPackageName(LongPackageName,SearchAssetDepTypes,OutRefAsset); +} + +void UFlibAssetManageHelper::GetAssetReferenceRecursively(const FAssetDetail& InAsset, + const TArray& + SearchAssetDepTypes, + const TArray& SearchAssetsTypes, + TArray& OutRefAsset, bool bRecursive) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::GetAssetReferenceRecursively",FColor::Red); + TArray CurrentAssetsRef; + UFlibAssetManageHelper::GetAssetReference(InAsset,SearchAssetDepTypes,CurrentAssetsRef); + + auto MatchAssetsTypesLambda = [](const FAssetDetail& InAsset,const TArray& SearchAssetsTypes)->bool + { + bool bresult = false; + if(SearchAssetsTypes.Num() > 0) + { + for(const auto& AssetType:SearchAssetsTypes) + { + if(InAsset.AssetType == FName(*AssetType)) + { + bresult = true; + break; + } + } + } + else + { + bresult = true; + } + return bresult; + }; + + for(const auto& AssetRef:CurrentAssetsRef) + { + if(MatchAssetsTypesLambda(AssetRef,SearchAssetsTypes)) + { + if(!OutRefAsset.Contains(AssetRef)) + { + OutRefAsset.AddUnique(AssetRef); + if(bRecursive) + { + UFlibAssetManageHelper::GetAssetReferenceRecursively(AssetRef,SearchAssetDepTypes,SearchAssetsTypes,OutRefAsset, bRecursive); + } + } + } + } +} + +bool UFlibAssetManageHelper::GetAssetReferenceEx(const FAssetDetail& InAsset, const TArray& SearchAssetDepTypes, TArray& OutRefAsset) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::GetAssetReferenceEx",FColor::Red); + TArray local_SearchAssetDepTypes; + for (const auto& type : SearchAssetDepTypes) + { + local_SearchAssetDepTypes.AddUnique(UFlibAssetManageHelper::ConvAssetRegistryDependencyToInternal(type)); + } + + return UFlibAssetManageHelper::GetAssetReference(InAsset, local_SearchAssetDepTypes, OutRefAsset); +} + +FName UFlibAssetManageHelper::GetAssetType(FSoftObjectPath SoftObjectPath) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::GetAssetTypeByPackageName",FColor::Red); + UAssetManager& AssetManager = UAssetManager::Get(); + FAssetData OutAssetData = AssetManager.GetAssetRegistry().GetAssetByObjectPath( +#if !UE_VERSION_OLDER_THAN(5,0,0) + SoftObjectPath, +#else +*SoftObjectPath.GetAssetPathString(), +#endif + bIncludeOnlyOnDiskAssets); + // UAssetManager::Get().GetAssetDataForPath(SoftObjectPath, OutAssetData) && OutAssetData.IsValid(); + + return UFlibAssetManageHelper::GetAssetDataClasses(OutAssetData); +} + +FAssetDetail UFlibAssetManageHelper::GetAssetDetailByPackageName(const FString& InPackageName) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::GetAssetDetailByPackageName",FColor::Red); + FAssetDetail AssetDetail; + UAssetManager& AssetManager = UAssetManager::Get(); + if (AssetManager.IsValid()) + { + FString PackagePath = UFlibAssetManageHelper::LongPackageNameToPackagePath(InPackageName); + { + FAssetData OutAssetData = AssetManager.GetAssetRegistry().GetAssetByObjectPath( +#if !UE_VERSION_OLDER_THAN(5,0,0) + FSoftObjectPath(PackagePath), +#else + FName(*PackagePath), +#endif + bIncludeOnlyOnDiskAssets); + if(OutAssetData.IsValid()) + { + AssetDetail.PackagePath = UFlibAssetManageHelper::GetObjectPathByAssetData(OutAssetData); + AssetDetail.AssetType = UFlibAssetManageHelper::GetAssetDataClasses(OutAssetData); +#if ENGINE_MAJOR_VERSION > 4 + UFlibAssetManageHelper::GetAssetPackageGUID(AssetDetail.PackagePath.ToString(), AssetDetail.Guid); +#else + UFlibAssetManageHelper::GetAssetPackageGUID(InPackageName, AssetDetail.Guid); +#endif + } + } + } + return AssetDetail; +} + +bool UFlibAssetManageHelper::GetRedirectorList(const TArray& InFilterPackagePaths, TArray& OutRedirector) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::GetRedirectorList",FColor::Red); + TArray AllAssetData; + if (UFlibAssetManageHelper::GetAssetsData(InFilterPackagePaths, AllAssetData,true)) + { + for (const auto& AssetDataIndex : AllAssetData) + { + if (AssetDataIndex.IsValid() && AssetDataIndex.IsRedirector()) + { + FAssetDetail AssetDetail; + UFlibAssetManageHelper::ConvFAssetDataToFAssetDetail(AssetDataIndex, AssetDetail); + OutRedirector.Add(AssetDetail); + } + } + return true; + } + return false; +} + +bool UFlibAssetManageHelper::IsRedirector(const FAssetDetail& Src,FAssetDetail& Out) +{ + SCOPED_NAMED_EVENT_TEXT("IsRedirector",FColor::Red); + bool bIsRedirector = false; + FAssetData AssetData; + if (UFlibAssetManageHelper::GetSingleAssetsData(Src.PackagePath.ToString(), AssetData)) + { + if ((AssetData.IsValid() && AssetData.IsRedirector()) || + UObjectRedirector::StaticClass()->GetFName() == Src.AssetType || + Src.PackagePath != UFlibAssetManageHelper::GetObjectPathByAssetData(AssetData) + ) + { + FAssetDetail AssetDetail; + UFlibAssetManageHelper::ConvFAssetDataToFAssetDetail(AssetData, AssetDetail); + Out = AssetDetail; + bIsRedirector = true; + } + } + return bIsRedirector; +} + +bool UFlibAssetManageHelper::GetSpecifyAssetData(const FString& InLongPackageName, TArray& OutAssetData, bool InIncludeOnlyOnDiskAssets) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::GetSpecifyAssetData",FColor::Red); + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + return AssetRegistryModule.Get().GetAssetsByPackageName(*InLongPackageName, OutAssetData, InIncludeOnlyOnDiskAssets); +} + +bool UFlibAssetManageHelper::GetAssetsData(const TArray& InFilterPaths, TArray& OutAssetData, bool bLocalIncludeOnlyOnDiskAssets) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::GetAssetsData",FColor::Red); + OutAssetData.Reset(); + + FARFilter Filter; +#if (ENGINE_MAJOR_VERSION > 4) || (ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION > 25) + Filter.WithoutPackageFlags = PKG_FilterEditorOnly; +#endif + Filter.bIncludeOnlyOnDiskAssets = bLocalIncludeOnlyOnDiskAssets; + Filter.bRecursivePaths = true; + for(const auto& FilterPackageName: InFilterPaths) + { + FString ValidFilterPackageName = FilterPackageName; + +#if ENGINE_MAJOR_VERSION > 4 + if(ValidFilterPackageName.StartsWith(TEXT("/All/"),ESearchCase::IgnoreCase)) + { + ValidFilterPackageName.RemoveFromStart(TEXT("/All"),ESearchCase::IgnoreCase); + } +#endif + + while (ValidFilterPackageName.EndsWith("/")) + { + ValidFilterPackageName.RemoveAt(ValidFilterPackageName.Len() - 1); + } + Filter.PackagePaths.AddUnique(*ValidFilterPackageName); + } + + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + AssetRegistryModule.Get().GetAssets(Filter, OutAssetData); + + return true; +} + +// /Game +TArray UFlibAssetManageHelper::GetAssetsByFilter(const FString& InFilter) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::GetAssetsByFilter",FColor::Red); + TArray result; + FString ContentPath = FPackageName::LongPackageNameToFilename(InFilter); + TArray AssetFiles; + TArray MapFiles; + IFileManager::Get().FindFilesRecursive(AssetFiles, *ContentPath, *(FString(TEXT("*")) + FPackageName::GetAssetPackageExtension()), true, false); + IFileManager::Get().FindFilesRecursive(MapFiles, *ContentPath, *(FString(TEXT("*")) + FPackageName::GetMapPackageExtension()), true, false); + result.Append(AssetFiles); + result.Append(MapFiles); + return result; +} + +bool UFlibAssetManageHelper::GetAssetsDataByDisk(const TArray& InFilterPaths, + TArray& OutAssetData) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::GetAssetsDataByDisk",FColor::Red); + TSet PackageNames; + { + TArray AssetPaths; + + for(const auto& Filter:InFilterPaths) + { + AssetPaths.Append(GetAssetsByFilter(Filter)); + } + for(auto& Asset:AssetPaths) + { + FString PackageName; + if(FPackageName::TryConvertFilenameToLongPackageName(Asset,PackageName)) + { + PackageNames.Add(FName(*PackageName)); + } + } + } + + for(const auto& PackageName:PackageNames) + { + FAssetData CurrentAssetData; + if(GetSingleAssetsData(PackageName.ToString(),CurrentAssetData)) + { + OutAssetData.AddUnique(CurrentAssetData); + } + } + return true; +} + +bool UFlibAssetManageHelper::GetSingleAssetsData(const FString& InPackagePath, FAssetData& OutAssetData) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::GetSingleAssetsData",FColor::Red); + UAssetManager& AssetManager = UAssetManager::Get(); + + OutAssetData = AssetManager.GetAssetRegistry().GetAssetByObjectPath( +#if !UE_VERSION_OLDER_THAN(5,0,0) + FSoftObjectPath(InPackagePath), +#else + FName(*InPackagePath), +#endif + bIncludeOnlyOnDiskAssets); + + return OutAssetData.IsValid();//AssetManager.GetAssetDataForPath(FSoftObjectPath{ InPackagePath }, OutAssetData); +} + +bool UFlibAssetManageHelper::GetAssetsDataByPackageName(const FString& InPackageName, FAssetData& OutAssetData) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::GetAssetsDataByPackageName",FColor::Red); + FString PackagePath = UFlibAssetManageHelper::LongPackageNameToPackagePath(InPackageName); + return UFlibAssetManageHelper::GetSingleAssetsData(PackagePath,OutAssetData); +} + +bool UFlibAssetManageHelper::GetClassStringFromFAssetData(const FAssetData& InAssetData, FString& OutAssetType) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::GetClassStringFromFAssetData",FColor::Red); + bool bRunState = false; + if (InAssetData.IsValid()) + { + OutAssetType = UFlibAssetManageHelper::GetAssetDataClasses(InAssetData).ToString(); + bRunState = true; + } + return bRunState; +} + + +bool UFlibAssetManageHelper::ConvFAssetDataToFAssetDetail(const FAssetData& InAssetData, FAssetDetail& OutAssetDetail) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::ConvFAssetDataToFAssetDetail",FColor::Red); + if (!InAssetData.IsValid()) + return false; + FAssetDetail AssetDetail; + AssetDetail.AssetType = UFlibAssetManageHelper::GetAssetDataClasses(InAssetData); + FString PackageName = InAssetData.PackageName.ToString(); + FString PackagePath = UFlibAssetManageHelper::LongPackageNameToPackagePath(PackageName); + AssetDetail.PackagePath = FName(*PackagePath); +#if ENGINE_MAJOR_VERSION > 4 + UFlibAssetManageHelper::GetAssetPackageGUID(PackagePath, AssetDetail.Guid); +#else + UFlibAssetManageHelper::GetAssetPackageGUID(PackageName, AssetDetail.Guid); +#endif + + OutAssetDetail = AssetDetail; + return OutAssetDetail.IsValid(); +} + +bool UFlibAssetManageHelper::GetSpecifyAssetDetail(const FString& InLongPackageName, FAssetDetail& OutAssetDetail) +{ + + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::GetSpecifyAssetDetail",FColor::Red); + bool bRunStatus = false; + TArray AssetData; + if (UFlibAssetManageHelper::GetSpecifyAssetData(InLongPackageName, AssetData, bIncludeOnlyOnDiskAssets)) + { + if (AssetData.Num() > 0) + { + UFlibAssetManageHelper::ConvFAssetDataToFAssetDetail(AssetData[0], OutAssetDetail); + bRunStatus = true; + } + } + if(!bRunStatus) + { +#if ASSET_DEPENDENCIES_DEBUG_LOG + SCOPED_NAMED_EVENT_TEXT("display GetSpecifyAssetDetail failed log",FColor::Red); + UE_LOG(LogHotPatcher,Log,TEXT("Get %s AssetDetail failed!"),*InLongPackageName); +#endif + } + return bRunStatus; +} + +void UFlibAssetManageHelper::FilterNoRefAssets(const TArray& InAssetsDetail, TArray& OutHasRefAssetsDetail, TArray& OutDontHasRefAssetsDetail) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::FilterNoRefAssets",FColor::Red); + OutHasRefAssetsDetail.Reset(); + OutDontHasRefAssetsDetail.Reset(); + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + for (const auto& AssetDetail : InAssetsDetail) + { + FSoftObjectPath CurrentObjectSoftRef{ AssetDetail.PackagePath.ToString() }; + FAssetIdentifier CurrentAssetId{ *CurrentObjectSoftRef.GetLongPackageName() }; + + // ignore scan Map Asset reference + { + FAssetData CurrentAssetData; + if (UFlibAssetManageHelper::GetSingleAssetsData(AssetDetail.PackagePath.ToString(), CurrentAssetData) && CurrentAssetData.IsValid()) + { + FName ClassName = UFlibAssetManageHelper::GetAssetDataClasses(CurrentAssetData); + if (ClassName == TEXT("World") || + ClassName == TEXT("MapBuildDataRegistry") + ) + { + OutHasRefAssetsDetail.Add(AssetDetail); + continue; + } + } + } + + + TArray CurrentAssetRefList; + AssetRegistryModule.Get().GetReferencers(CurrentAssetId, CurrentAssetRefList); + if (CurrentAssetRefList.Num() > 1 || (CurrentAssetRefList.Num() > 0 && !(CurrentAssetRefList[0] == CurrentAssetId))) + { + OutHasRefAssetsDetail.Add(AssetDetail); + } + else { + OutDontHasRefAssetsDetail.Add(AssetDetail); + } + } +} + +void UFlibAssetManageHelper::FilterNoRefAssetsWithIgnoreFilter(const TArray& InAssetsDetail, const TArray& InIgnoreFilters, TArray& OutHasRefAssetsDetail, TArray& OutDontHasRefAssetsDetail) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::FilterNoRefAssetsWithIgnoreFilter",FColor::Red); + OutHasRefAssetsDetail.Reset(); + OutDontHasRefAssetsDetail.Reset(); + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + for (const auto& AssetDetail : InAssetsDetail) + { + FSoftObjectPath CurrentObjectSoftRef{ AssetDetail.PackagePath.ToString() }; + FAssetIdentifier CurrentAssetId{ *CurrentObjectSoftRef.GetLongPackageName() }; + + // ignore scan Map Asset reference + { + FAssetData CurrentAssetData; + if (UFlibAssetManageHelper::GetSingleAssetsData(AssetDetail.PackagePath.ToString(), CurrentAssetData) && CurrentAssetData.IsValid()) + { + FName ClassName = UFlibAssetManageHelper::GetAssetDataClasses(CurrentAssetData); + if (ClassName == TEXT("World") || + ClassName == TEXT("MapBuildDataRegistry") + ) + { + bool bIsIgnoreAsset = false; + for (const auto& IgnoreFilter : InIgnoreFilters) + { + if (CurrentAssetData.PackagePath.ToString().StartsWith(*IgnoreFilter)) + { + bIsIgnoreAsset = true; + break; + } + } + + if (!bIsIgnoreAsset) + { + OutHasRefAssetsDetail.Add(AssetDetail); + continue; + } + } + } + } + + + TArray CurrentAssetRefList; + AssetRegistryModule.Get().GetReferencers(CurrentAssetId, CurrentAssetRefList); + if (CurrentAssetRefList.Num() > 1 || (CurrentAssetRefList.Num() > 0 && !(CurrentAssetRefList[0] == CurrentAssetId))) + { + OutHasRefAssetsDetail.Add(AssetDetail); + } + else { + OutDontHasRefAssetsDetail.Add(AssetDetail); + } + } +} +bool UFlibAssetManageHelper::CombineAssetsDetailAsFAssetDepenInfo(const TArray& InAssetsDetailList, FAssetDependenciesInfo& OutAssetInfo) +{ +SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::CombineAssetsDetailAsFAssetDepenInfo",FColor::Red); + FAssetDependenciesInfo result; + + for (const auto& AssetDetail : InAssetsDetailList) + { + FString CurrAssetModuleName = UFlibAssetManageHelper::GetAssetBelongModuleName(AssetDetail.PackagePath.ToString()); + FSoftObjectPath CurrAssetObjectPath(AssetDetail.PackagePath.ToString()); + FString CurrAssetLongPackageName = CurrAssetObjectPath.GetLongPackageName(); + if (!result.AssetsDependenciesMap.Contains(CurrAssetModuleName)) + { + FAssetDependenciesDetail AssetDependenciesDetail{ CurrAssetModuleName,TMap{ {CurrAssetLongPackageName,AssetDetail} } }; + result.AssetsDependenciesMap.Add(CurrAssetModuleName, AssetDependenciesDetail); + } + else + { + FAssetDependenciesDetail& CurrentCategory = *result.AssetsDependenciesMap.Find(CurrAssetModuleName); + + if (!result.AssetsDependenciesMap.Contains(CurrAssetLongPackageName)) + { + CurrentCategory.AssetDependencyDetails.Add(CurrAssetLongPackageName,AssetDetail); + } + } + + } + OutAssetInfo = result; + + return true; +} + + + +void UFlibAssetManageHelper::GetAllInValidAssetInProject(FAssetDependenciesInfo InAllDependencies, TArray &OutInValidAsset,TArray InIgnoreModules) +{ +SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::GetAllInValidAssetInProject",FColor::Red); + TArray Keys; + InAllDependencies.AssetsDependenciesMap.GetKeys(Keys); + + for (const auto& ModuleItem : Keys) + { + // ignore search /Script Asset + if (InIgnoreModules.Contains(ModuleItem)) + continue; + FAssetDependenciesDetail ModuleDependencies = InAllDependencies.AssetsDependenciesMap[ModuleItem]; + + TArray ModuleAssetList; + ModuleDependencies.AssetDependencyDetails.GetKeys(ModuleAssetList); + for (const auto& AssetLongPackageName : ModuleAssetList) + { + FString AssetAbsPath = UFlibAssetManageHelper::LongPackageNameToFilename(AssetLongPackageName); + if (!FPaths::FileExists(AssetAbsPath)) + { + OutInValidAsset.Add(AssetLongPackageName); + } + } + } +} +#pragma warning(push) +#pragma warning(disable:4172) +FAssetPackageData* UFlibAssetManageHelper::GetPackageDataByPackageName(const FString& InPackageName) +{ + FAssetPackageData* AssetPackageData = nullptr; + if (InPackageName.IsEmpty()) + return NULL; + if (!InPackageName.IsEmpty()) + { + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + // FString TargetLongPackageName = UFlibAssetManageHelper::PackagePathToLongPackageName(InPackageName); + const FString& TargetLongPackageName = InPackageName; +#if ENGINE_MAJOR_VERSION > 4 /*&& ENGINE_MINOR_VERSION > 0*/ + TOptional PackageDataOpt = AssetRegistryModule.Get().GetAssetPackageDataCopy(*TargetLongPackageName); + if(PackageDataOpt.IsSet()) + { + const FAssetPackageData* PackageData = &PackageDataOpt.GetValue(); +#else + const FAssetPackageData* PackageData = AssetRegistryModule.Get().GetAssetPackageData(*TargetLongPackageName); +#endif + AssetPackageData = const_cast(PackageData); + if (AssetPackageData != nullptr) + { + return AssetPackageData; + } +#if ENGINE_MAJOR_VERSION > 4 /*&& ENGINE_MINOR_VERSION > 0*/ + } +#endif + } + + return NULL; +} +#pragma warning(pop) + +bool UFlibAssetManageHelper::ConvLongPackageNameToCookedPath( + const FString& InProjectAbsDir, + const FString& InPlatformName, + const FString& InLongPackageName, + TArray& OutCookedAssetPath, + TArray& OutCookedAssetRelativePath, + const FString& OverrideCookedDir + ) +{ + if (!FPaths::DirectoryExists(InProjectAbsDir)/* || !IsValidPlatform(InPlatformName)*/) + return false; + + FString EngineAbsDir = FPaths::ConvertRelativePathToFull(FPaths::EngineDir()); + FString CookedRootDir = FPaths::Combine(InProjectAbsDir, TEXT("Saved/Cooked"), InPlatformName); + if(!OverrideCookedDir.IsEmpty() && FPaths::DirectoryExists(OverrideCookedDir)) + { + CookedRootDir = FPaths::Combine(OverrideCookedDir,InPlatformName); + } + FString ProjectName = FApp::GetProjectName(); + // FString AssetPackagePath = UFlibAssetManageHelper::LongPackageNameToPackagePath(InLongPackageName); + FString AssetAbsPath = UFlibAssetManageHelper::LongPackageNameToFilename(InLongPackageName); + + FString AssetModuleName; + GetModuleNameByRelativePath(InLongPackageName,AssetModuleName); + + bool bIsEngineModule = false; + FString AssetBelongModuleBaseDir; + FString AssetCookedNotPostfixPath; + { + + if (UFlibAssetManageHelper::GetEnableModuleAbsDir(AssetModuleName, AssetBelongModuleBaseDir)) + { + if (AssetBelongModuleBaseDir.Contains(EngineAbsDir)) + bIsEngineModule = true; + } + + FString AssetCookedRelativePath; + if (bIsEngineModule) + { + AssetCookedRelativePath = TEXT("Engine") / UKismetStringLibrary::GetSubstring(AssetAbsPath, EngineAbsDir.Len() - 1, AssetAbsPath.Len() - EngineAbsDir.Len()); + } + else + { + AssetCookedRelativePath = ProjectName / UKismetStringLibrary::GetSubstring(AssetAbsPath, InProjectAbsDir.Len() - 1, AssetAbsPath.Len() - InProjectAbsDir.Len()); + } + + // remove .uasset / .umap postfix + { + int32 lastDotIndex = 0; + AssetCookedRelativePath.FindLastChar('.', lastDotIndex); + AssetCookedRelativePath.RemoveAt(lastDotIndex, AssetCookedRelativePath.Len() - lastDotIndex); + } + + AssetCookedNotPostfixPath = FPaths::Combine(CookedRootDir, AssetCookedRelativePath); + } + + FFileArrayDirectoryVisitor FileVisitor; + FString SearchDir; + { + int32 lastSlashIndex; + AssetCookedNotPostfixPath.FindLastChar('/', lastSlashIndex); + SearchDir = UKismetStringLibrary::GetSubstring(AssetCookedNotPostfixPath, 0, lastSlashIndex); + } + IFileManager::Get().IterateDirectory(*SearchDir, FileVisitor); + for (const auto& FileItem : FileVisitor.Files) + { + if (FileItem.Contains(AssetCookedNotPostfixPath) && FileItem[AssetCookedNotPostfixPath.Len()] == '.') + { + OutCookedAssetPath.Add(FileItem); + { + FString AssetCookedRelativePath = UKismetStringLibrary::GetSubstring(FileItem, CookedRootDir.Len() + 1, FileItem.Len() - CookedRootDir.Len()); + OutCookedAssetRelativePath.Add(FPaths::Combine(TEXT("../../../"), AssetCookedRelativePath)); + } + } + } + return true; +} + + +bool UFlibAssetManageHelper::MakePakCommandFromAssetDependencies( + const FString& InProjectDir, + const FString& OverrideCookedDir, + const FString& InPlatformName, + const FAssetDependenciesInfo& InAssetDependencies, + //const TArray &InCookParams, + TArray& OutCookCommand, + TFunction&,const TArray&, const FString&, const FString&)> InReceivePakCommand, + TFunction IsIoStoreAsset +) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::MakePakCommandFromAssetDependencies",FColor::Red); + if (!FPaths::DirectoryExists(InProjectDir) /*|| !UFlibAssetManageHelper::IsValidPlatform(InPlatformName)*/) + return false; + OutCookCommand.Empty(); + // TArray resault; + TArray Keys; + InAssetDependencies.AssetsDependenciesMap.GetKeys(Keys); + FCriticalSection LocalSynchronizationObject; + ParallelFor(Keys.Num(),[&](int32 index) + { + const FString& Key = Keys[index]; + if (Key.Equals(TEXT("Script"))) + return; + TArray AssetList; + InAssetDependencies.AssetsDependenciesMap.Find(Key)->AssetDependencyDetails.GetKeys(AssetList); + for (const auto& AssetLongPackageName : AssetList) + { + TArray FinalCookedCommand; + if (UFlibAssetManageHelper::MakePakCommandFromLongPackageName(InProjectDir, OverrideCookedDir,InPlatformName, AssetLongPackageName, /*InCookParams, */FinalCookedCommand,InReceivePakCommand,IsIoStoreAsset)) + { + FScopeLock Lock(&LocalSynchronizationObject); + OutCookCommand.Append(FinalCookedCommand); + } + } + },true); + return true; +} + + +bool UFlibAssetManageHelper::MakePakCommandFromLongPackageName( + const FString& InProjectDir, + const FString& OverrideCookedDir, + const FString& InPlatformName, + const FString& InAssetLongPackageName, + //const TArray &InCookParams, + TArray& OutCookCommand, + TFunction&,const TArray&, const FString&, const FString&)> InReceivePakCommand, + TFunction IsIoStoreAsset +) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::MakePakCommandFromLongPackageName",FColor::Red); + OutCookCommand.Empty(); + bool bStatus = false; + TArray CookedAssetAbsPath; + TArray CookedAssetRelativePath; + TArray FinalCookedPakCommand; + TArray FinalCookedIoStoreCommand; + if (UFlibAssetManageHelper::ConvLongPackageNameToCookedPath(InProjectDir, InPlatformName, InAssetLongPackageName, CookedAssetAbsPath, CookedAssetRelativePath,OverrideCookedDir)) + { + if (UFlibAssetManageHelper::CombineCookedAssetCommand(CookedAssetAbsPath, CookedAssetRelativePath,/* InCookParams,*/ FinalCookedPakCommand,FinalCookedIoStoreCommand,IsIoStoreAsset)) + { + if (!!CookedAssetRelativePath.Num() && !!FinalCookedPakCommand.Num()) + { + InReceivePakCommand(FinalCookedPakCommand,FinalCookedIoStoreCommand, CookedAssetRelativePath[0],InAssetLongPackageName); + OutCookCommand.Append(FinalCookedPakCommand); + bStatus = true; + } + } + } + return bStatus; +} + +bool UFlibAssetManageHelper::CombineCookedAssetCommand( + const TArray &InAbsPath, + const TArray& InRelativePath, + //const TArray& InParams, + TArray& OutPakCommand, + TArray& OutIoStoreCommand, + TFunction IsIoStoreAsset +) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::CombineCookedAssetCommand",FColor::Red); + if (!(!!InAbsPath.Num() && !!InRelativePath.Num())) + { + return false; + } + + OutPakCommand.Empty(); + if (InAbsPath.Num() != InRelativePath.Num()) + return false; + int32 AssetNum = InAbsPath.Num(); + for (int32 index = 0; index < AssetNum; ++index) + { + if(InAbsPath[index].EndsWith(TEXT(".patch"))) + continue;; + FString CurrentCommand = TEXT("\"") + InAbsPath[index] + TEXT("\" \"") + InRelativePath[index] + TEXT("\""); + // for (const auto& Param : InParams) + // { + // CurrentCommand += TEXT(" ") + Param; + // } + if(!IsIoStoreAsset(InAbsPath[index])) + OutPakCommand.Add(CurrentCommand); + else + OutIoStoreCommand.Add(CurrentCommand); + } + return true; +} + +bool UFlibAssetManageHelper::ExportCookPakCommandToFile(const TArray& InCommand, const FString& InFile) +{ + return FFileHelper::SaveStringArrayToFile(InCommand, *InFile); +} + +bool UFlibAssetManageHelper::SaveStringToFile(const FString& InFile, const FString& InString) +{ + return FFileHelper::SaveStringToFile(InString, *InFile, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM); +} + +bool UFlibAssetManageHelper::LoadFileToString(const FString& InFile, FString& OutString) +{ + return FFileHelper::LoadFileToString(OutString, *InFile); +} + +bool UFlibAssetManageHelper::GetPluginModuleAbsDir(const FString& InPluginModuleName, FString& OutPath) +{ + bool bFindResault = false; + TSharedPtr FoundModule = IPluginManager::Get().FindPlugin(InPluginModuleName); + + if (FoundModule.IsValid()) + { + bFindResault = true; + OutPath = FPaths::ConvertRelativePathToFull(FoundModule->GetBaseDir()); + } + return bFindResault; +} + +void UFlibAssetManageHelper::GetAllEnabledModuleName(TMap& OutModules) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::GetAllEnabledModuleName",FColor::Red); + OutModules.Add(TEXT("Game"), FPaths::ProjectDir()); + OutModules.Add(TEXT("Engine"), FPaths::EngineDir()); + + TArray> AllPlugin = IPluginManager::Get().GetEnabledPlugins(); + + for (const auto& PluginItem : AllPlugin) + { + OutModules.Add(PluginItem.Get().GetName(), PluginItem.Get().GetBaseDir()); + } +} + + +bool UFlibAssetManageHelper::ModuleIsEnabled(const FString& InModuleName) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::ModuleIsEnabled",FColor::Red); + TMap AllEnabledModules; + UFlibAssetManageHelper::GetAllEnabledModuleName(AllEnabledModules); + TArray AllEnableModuleNames; + AllEnabledModules.GetKeys(AllEnableModuleNames); + return AllEnabledModules.Contains(InModuleName); +} + +bool UFlibAssetManageHelper::GetModuleNameByRelativePath(const FString& InRelativePath, FString& OutModuleName) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::GetModuleNameByRelativePath",FColor::Red); + if (InRelativePath.IsEmpty()) return false; + FString BelongModuleName = InRelativePath; + { + BelongModuleName.RemoveFromStart(TEXT("/")); + int32 secondSlashIndex = -1; + if(BelongModuleName.FindChar('/', secondSlashIndex)) + BelongModuleName = UKismetStringLibrary::GetSubstring(BelongModuleName, 0, secondSlashIndex); + } + OutModuleName = BelongModuleName; + return true; +} + +bool UFlibAssetManageHelper::GetEnableModuleAbsDir(const FString& InModuleName, FString& OutPath) +{ + if (InModuleName.Equals(TEXT("Engine"))) + { + OutPath = FPaths::ConvertRelativePathToFull(FPaths::EngineDir()); + return true; + } + if (InModuleName.Equals(TEXT("Game")) || InModuleName.Equals(FApp::GetProjectName())) + { + OutPath = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir()); + return true; + } + return UFlibAssetManageHelper::GetPluginModuleAbsDir(InModuleName, OutPath); +} + +FString UFlibAssetManageHelper::GetAssetBelongModuleName(const FString& InAssetRelativePath) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::GetAssetBelongModuleName",FColor::Red); + FString LongDependentPackageName = InAssetRelativePath; + + int32 BelongModuleNameEndIndex = LongDependentPackageName.Find(TEXT("/"), ESearchCase::IgnoreCase, ESearchDir::FromStart, 1); + FString BelongModuleName = UKismetStringLibrary::GetSubstring(LongDependentPackageName, 1, BelongModuleNameEndIndex - 1);// (LongDependentPackageName, BelongModuleNameEndIndex); + + return BelongModuleName; +} + + +bool UFlibAssetManageHelper::ConvRelativeDirToAbsDir(const FString& InRelativePath, FString& OutAbsPath) +{ + bool bRunStatus = false; + FString BelongModuleName; + if (UFlibAssetManageHelper::GetModuleNameByRelativePath(InRelativePath, BelongModuleName)) + { + if (UFlibAssetManageHelper::ModuleIsEnabled(BelongModuleName)) + { + FString ModuleAbsPath; + if (!UFlibAssetManageHelper::GetEnableModuleAbsDir(BelongModuleName, ModuleAbsPath)) + return false; + + FString RelativeToModule = InRelativePath.Replace(*BelongModuleName, TEXT("Content")); + RelativeToModule = RelativeToModule.StartsWith(TEXT("/")) ? RelativeToModule.Right(RelativeToModule.Len() - 1) : RelativeToModule; + FString FinalFilterPath = FPaths::Combine(ModuleAbsPath, RelativeToModule); + FPaths::MakeStandardFilename(FinalFilterPath); + if (FPaths::DirectoryExists(FinalFilterPath)) + { + OutAbsPath = FinalFilterPath; + bRunStatus = true; + } + } + } + return bRunStatus; +} + +bool UFlibAssetManageHelper::FindFilesRecursive(const FString& InStartDir, TArray& OutFileList, bool InRecursive) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::FindFilesRecursive",FColor::Red); + TArray CurrentFolderFileList; + if (!FPaths::DirectoryExists(InStartDir)) + return false; + + FFileArrayDirectoryVisitor FileVisitor; + IFileManager::Get().IterateDirectoryRecursively(*InStartDir, FileVisitor); + + OutFileList.Append(FileVisitor.Files); + + return true; +} + +uint32 UFlibAssetManageHelper::ParserAssetDependenciesInfoNumber(const FAssetDependenciesInfo& AssetDependenciesInfo, TMap ModuleAssetsNumMaps) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::ParserAssetDependenciesInfoNumber",FColor::Red); + uint32 TotalAssetNum = 0; + TArray ModuleCategorys; + AssetDependenciesInfo.AssetsDependenciesMap.GetKeys(ModuleCategorys); + for(const auto& ModuleKey:ModuleCategorys) + { + TArray AssetKeys; + AssetDependenciesInfo.AssetsDependenciesMap.Find(ModuleKey)->AssetDependencyDetails.GetKeys(AssetKeys); + ModuleAssetsNumMaps.Add(ModuleKey,AssetKeys.Num()); + TotalAssetNum+=AssetKeys.Num(); + } + return TotalAssetNum; +} + +FString UFlibAssetManageHelper::ParserModuleAssetsNumMap(const TMap& InMap) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::ParserModuleAssetsNumMap",FColor::Red); + FString result; + TArray Keys; + InMap.GetKeys(Keys); + + for(const auto& Key:Keys) + { + result += FString::Printf(TEXT("%s\t%d\n"),*Key,*InMap.Find(Key)); + } + return result; +} + +EAssetRegistryDependencyType::Type UFlibAssetManageHelper::ConvAssetRegistryDependencyToInternal(const EAssetRegistryDependencyTypeEx& InType) +{ + return static_cast((uint8)(InType)); +} + +void UFlibAssetManageHelper::GetAssetDataInPaths(const TArray& Paths, TArray& OutAssetData) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::GetAssetDataInPaths",FColor::Red); + // Form a filter from the paths + FARFilter Filter; + Filter.bRecursivePaths = true; + for (const FString& Path : Paths) + { + new (Filter.PackagePaths) FName(*Path); + } + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + + AssetRegistryModule.Get().GetAssets(Filter, OutAssetData); +} + +FString UFlibAssetManageHelper::LongPackageNameToPackagePath(const FString& InLongPackageName) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::LongPackageNameToPackagePath",FColor::Red); + if(InLongPackageName.Contains(TEXT("."))) + { + // UE_LOG(LogHotPatcher,Warning,TEXT("LongPackageNameToPackagePath %s alway is PackagePath!"),*InLongPackageName); + return InLongPackageName; + } + FString AssetName; + { + int32 FoundIndex; + InLongPackageName.FindLastChar('/', FoundIndex); + if (FoundIndex != INDEX_NONE) + { + AssetName = UKismetStringLibrary::GetSubstring(InLongPackageName, FoundIndex + 1, InLongPackageName.Len() - FoundIndex); + } + } + FString OutPackagePath = InLongPackageName + TEXT(".") + AssetName; + return OutPackagePath; +} + +FString UFlibAssetManageHelper::PackagePathToLongPackageName(const FString& PackagePath) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibAssetManageHelper::PackagePathToLongPackageName",FColor::Red); + FSoftObjectPath ObjectPath = PackagePath; + return ObjectPath.GetLongPackageName(); +} + +void UFlibAssetManageHelper::ExcludeContentForAssetDependenciesDetail(FAssetDependenciesInfo& AssetDependencies,const TArray& ExcludeRules,EHotPatcherMatchModEx matchMod) +{ + SCOPED_NAMED_EVENT_TEXT("ExcludeContentForAssetDependenciesDetail",FColor::Red); + auto ExcludeEditorContent = [&ExcludeRules,matchMod](TMap& AssetCategorys) + { + TArray Keys; + AssetCategorys.GetKeys(Keys); + + for(const auto& Key:Keys) + { + FAssetDependenciesDetail& ModuleAssetList = *AssetCategorys.Find(Key); + TArray AssetKeys; + + ModuleAssetList.AssetDependencyDetails.GetKeys(AssetKeys); + FCriticalSection SynchronizationObject; + + // for(const auto& AssetKey:AssetKeys) + ParallelFor(AssetKeys.Num(),[&](int32 index) + { + auto& AssetKey = AssetKeys[index]; + bool customStartWith = false; + FString MatchRule; + for(const auto& Rule:ExcludeRules) + { + if((matchMod == EHotPatcherMatchModEx::StartWith && AssetKeys[index].StartsWith(Rule)) || + (matchMod == EHotPatcherMatchModEx::Equal && AssetKeys[index].Equals(Rule)) + ) + { + MatchRule = Rule; + customStartWith = true; + break; + } + } + if( customStartWith ) + { + // UE_LOG(LogHotPatcher,Display,TEXT("remove %s in AssetDependencies(match exclude rule %s)"),*AssetKey,*MatchRule); + FScopeLock Lock(&SynchronizationObject); + ModuleAssetList.AssetDependencyDetails.Remove(AssetKey); + } + },GForceSingleThread); + } + }; + ExcludeEditorContent(AssetDependencies.AssetsDependenciesMap); +} + +TArray UFlibAssetManageHelper::DirectoriesToStrings(const TArray& DirectoryPaths) +{ + TArray Path; + for(const auto& DirPath:DirectoryPaths) + { + Path.AddUnique(NormalizeContentDir(DirPath.Path)); + } + return Path; +} + +TArray UFlibAssetManageHelper::SoftObjectPathsToStrings(const TArray& SoftObjectPaths) +{ + TArray result; + for(const auto &Asset:SoftObjectPaths) + { + result.Add(Asset.GetLongPackageName()); + } + return result; +} + +TSet UFlibAssetManageHelper::GetClassesNames(const TArray CLasses) +{ + TSet ForceSkipTypes; + for(const auto& Classes:CLasses) + { + ForceSkipTypes.Add(*Classes->GetName()); + } + return ForceSkipTypes; +} + +FString UFlibAssetManageHelper::NormalizeContentDir(const FString& Dir) +{ + FString result = Dir; + +#if ENGINE_MAJOR_VERSION > 4 + if(result.StartsWith(TEXT("/All/"),ESearchCase::IgnoreCase)) + { + result.RemoveFromStart(TEXT("/All"),ESearchCase::IgnoreCase); + } +#endif + if(!Dir.EndsWith(TEXT("/"))) + { + result = FString::Printf(TEXT("%s/"),*result); + } + + return result; +} + +TArray UFlibAssetManageHelper::NormalizeContentDirs(const TArray& Dirs) +{ + TArray NormalizedDirs; + for(const auto& Dir:Dirs) + { + NormalizedDirs.AddUnique(UFlibAssetManageHelper::NormalizeContentDir(Dir)); + } + return NormalizedDirs; +} + + +FStreamableManager& UFlibAssetManageHelper::GetStreamableManager() +{ + return UAssetManager::GetStreamableManager(); +} + +TSharedPtr UFlibAssetManageHelper::LoadObjectAsync(FSoftObjectPath ObjectPath,TFunction Callback,uint32 Priority) +{ + TSharedPtr Handle = GetStreamableManager().RequestAsyncLoad(ObjectPath, FStreamableDelegate::CreateLambda([=]() + { + if(Callback) + { + Callback(ObjectPath); + } + }), Priority, false); + return Handle; +} + +void UFlibAssetManageHelper::LoadPackageAsync(FSoftObjectPath ObjectPath,TFunction Callback,uint32 Priority) +{ + ::LoadPackageAsync(ObjectPath.GetLongPackageName(), nullptr,nullptr,FLoadPackageAsyncDelegate::CreateLambda([=](const FName& PackageName, UPackage* Package, EAsyncLoadingResult::Type Result) + { + if(Callback && Result == EAsyncLoadingResult::Succeeded) + { + Package->AddToRoot(); + Callback(UFlibAssetManageHelper::LongPackageNameToPackagePath(Package->GetPathName()),Result == EAsyncLoadingResult::Succeeded); + } + })); +} + +UPackage* UFlibAssetManageHelper::LoadPackage(UPackage* InOuter, const TCHAR* InLongPackageName, uint32 LoadFlags, + FArchive* InReaderOverride) +{ +#if ENGINE_MINOR_VERSION < 26 + FScopedNamedEvent CookPackageEvent(FColor::Red,*FString::Printf(TEXT("LoadPackage %s"),InLongPackageName)); +#endif + UE_LOG(LogHotPatcher,Verbose,TEXT("Load %s,outer %s"),InLongPackageName,InOuter ? *InOuter->GetFullName():TEXT("null")); + return ::LoadPackage(InOuter,InLongPackageName,LoadFlags,InReaderOverride); +} + +UPackage* UFlibAssetManageHelper::GetPackage(FName PackageName) +{ +#if ENGINE_MINOR_VERSION < 26 + FScopedNamedEvent CookPackageEvent(FColor::Red,*FString::Printf(TEXT("GetPackage %s"),*PackageName.ToString())); +#endif + if (PackageName == NAME_None) + { + return NULL; + } + + UPackage* Package = FindPackage(NULL, *PackageName.ToString()); + if (Package) + { + Package->FullyLoad(); + } + else + { + Package = UFlibAssetManageHelper::LoadPackage(NULL, *PackageName.ToString(), LOAD_None); + } + + return Package; +} + +// TArray UFlibAssetManageHelper::GetPackagesByClass(TArray& Packages, UClass* Class, bool RemoveFromSrc) +// { +// // TArray result; +// // for(int32 PkgIndex = Packages.Num() - 1 ;PkgIndex >= 0;--PkgIndex) +// // { +// // if(Packages[PkgIndex]->IsA(Class)) +// // { +// // result.Add(Packages[PkgIndex]); +// // if(RemoveFromSrc) +// // { +// // Packages.RemoveAtSwap(PkgIndex,1,false); +// // } +// // +// // } +// // } +// // return result; +// return THotPatcherTemplateHelper::GetArrayBySrcWithCondition(Packages,[&](UPackage* Package)->bool +// { +// return Package->IsA(Class); +// },true); +// }; + +TArray UFlibAssetManageHelper::LoadPackagesForCooking(const TArray& SoftObjectPaths, bool bStorageConcurrent) +{ + SCOPED_NAMED_EVENT_TEXT("LoadPackagesForCooking",FColor::Red); + TArray AllPackages; + GIsCookerLoadingPackage = true; + for(const auto& Asset:SoftObjectPaths) + { + UPackage* Package = UFlibAssetManageHelper::GetPackage(*Asset.GetLongPackageName()); + if(!Package) + { + UE_LOG(LogHotPatcher,Warning,TEXT("LodPackage %s is null!"),*Asset.GetLongPackageName()); + continue; + } + AllPackages.AddUnique(Package); + + } + + for(auto Package:AllPackages) + { + if(!bStorageConcurrent && Package->IsFullyLoaded()) + { + UMetaData* MetaData = Package->GetMetaData(); + if(MetaData) + { + MetaData->RemoveMetaDataOutsidePackage(); + } + } + // Precache the metadata so we don't risk rehashing the map in the parallelfor below + if(bStorageConcurrent) + { + if(!Package->IsFullyLoaded()) + { + Package->FullyLoad(); + } + Package->GetMetaData(); + } + } + GIsCookerLoadingPackage = false; + return AllPackages; +} + +bool UFlibAssetManageHelper::MatchIgnoreTypes(const FString& LongPackageName, TSet IgnoreTypes, FString& MatchTypeStr) +{ + bool bIgnoreType = false; + FAssetData AssetData; + if(UFlibAssetManageHelper::GetAssetsDataByPackageName(LongPackageName,AssetData)) + { + SCOPED_NAMED_EVENT_TEXT("is ignore types",FColor::Red); + MatchTypeStr = UFlibAssetManageHelper::GetAssetDataClasses(AssetData).ToString(); + FName ClassName = UFlibAssetManageHelper::GetAssetDataClasses(AssetData); + bIgnoreType = IgnoreTypes.Contains(ClassName); + + if(!bIgnoreType) + { +#if (ENGINE_MAJOR_VERSION > 4) || (ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION > 25) + bIgnoreType = AssetData.HasAnyPackageFlags(PKG_EditorOnly); +#else + // bIgnoreType = (AssetData.PackageFlags & PKG_EditorOnly) != 0; + +#endif + if(bIgnoreType) + { + MatchTypeStr = TEXT("Has PKG_EditorOnly Flag"); + } + } + } + return bIgnoreType; +} + +bool UFlibAssetManageHelper::MatchIgnoreFilters(const FString& LongPackageName, const TArray& IgnoreDirs, + FString& MatchDir) +{ + for(const auto& IgnoreFilter:IgnoreDirs) + { + bool bWithInSkipDir = LongPackageName.StartsWith(IgnoreFilter); + bool bMatchSkipWildcard = IgnoreFilter.Contains(TEXT("*")) ? LongPackageName.MatchesWildcard(IgnoreFilter,ESearchCase::CaseSensitive) : false; + if( bWithInSkipDir || bMatchSkipWildcard) + { + MatchDir = IgnoreFilter; + return true; + } + } + return false; +} + + +bool UFlibAssetManageHelper::ContainsRedirector(const FName& PackageName, TMap& RedirectedPaths) +{ + bool bFoundRedirector = false; + TArray Assets; + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + IAssetRegistry* AssetRegistry = &AssetRegistryModule.Get(); + ensure(AssetRegistry->GetAssetsByPackageName(PackageName, Assets, true)); + + for (const FAssetData& Asset : Assets) + { + if (Asset.IsRedirector()) + { + FName RedirectedPath; + FString RedirectedPathString; + if (Asset.GetTagValue("DestinationObject", RedirectedPathString)) + { + ConstructorHelpers::StripObjectClass(RedirectedPathString); + RedirectedPath = FName(*RedirectedPathString); + FAssetData DestinationData = AssetRegistry->GetAssetByObjectPath(RedirectedPath, true); + TSet SeenPaths; + + SeenPaths.Add(RedirectedPath); + + // Need to follow chain of redirectors + while (DestinationData.IsRedirector()) + { + if (DestinationData.GetTagValue("DestinationObject", RedirectedPathString)) + { + ConstructorHelpers::StripObjectClass(RedirectedPathString); + RedirectedPath = FName(*RedirectedPathString); + + if (SeenPaths.Contains(RedirectedPath)) + { + // Recursive, bail + DestinationData = FAssetData(); + } + else + { + SeenPaths.Add(RedirectedPath); + DestinationData = AssetRegistry->GetAssetByObjectPath(RedirectedPath, true); + } + } + else + { + // Can't extract + DestinationData = FAssetData(); + } + } + + // DestinationData may be invalid if this is a subobject, check package as well + bool bDestinationValid = DestinationData.IsValid(); + + // if (!bDestinationValid) + // { + // // we can;t call GetCachedStandardPackageFileFName with None + // if (RedirectedPath != NAME_None) + // { + // FName StandardPackageName = PackageNameCache->GetCachedStandardPackageFileFName(FName(*FPackageName::ObjectPathToPackageName(RedirectedPathString))); + // if (StandardPackageName != NAME_None) + // { + // bDestinationValid = true; + // } + // } + // } + + if (bDestinationValid) + { + RedirectedPaths.Add(UFlibAssetManageHelper::GetObjectPathByAssetData(Asset), RedirectedPath); + } + else + { + RedirectedPaths.Add(UFlibAssetManageHelper::GetObjectPathByAssetData(Asset), NAME_None); + UE_LOG(LogHotPatcher, Log, TEXT("Found redirector in package %s pointing to deleted object %s"), *PackageName.ToString(), *RedirectedPathString); + } + + bFoundRedirector = true; + } + } + } + return bFoundRedirector; +} + + +TArray UFlibAssetManageHelper::FindClassObjectInPackage(UPackage* Package,UClass* FindClass) +{ + TArray ResultObjects; + TArray ObjectsInPackage; + GetObjectsWithOuter(Package, ObjectsInPackage, false); + for ( auto ObjIt = ObjectsInPackage.CreateConstIterator(); ObjIt; ++ObjIt ) + { + if((*ObjIt)->GetClass()->IsChildOf(FindClass)) + { + ResultObjects.Add(*ObjIt); + } + } + return ResultObjects; +} +bool UFlibAssetManageHelper::HasClassObjInPackage(UPackage* Package,UClass* FindClass) +{ + return !!FindClassObjectInPackage(Package,FindClass).Num(); +} + +TArray UFlibAssetManageHelper::GetAssetDetailsByClass(TArray& AllAssetDetails, + UClass* Class, bool RemoveFromSrc) +{ + SCOPED_NAMED_EVENT_TEXT("GetAssetDetailsByClass",FColor::Red); + return THotPatcherTemplateHelper::GetArrayBySrcWithCondition(AllAssetDetails,[&](FAssetDetail AssetDetail)->bool + { + return AssetDetail.AssetType.IsEqual(*Class->GetName()); + },RemoveFromSrc); +} + +TArray UFlibAssetManageHelper::GetAssetPathsByClass(TArray& AllAssetDetails, + UClass* Class, bool RemoveFromSrc) +{ + TArray ObjectPaths; + for(const auto& AssetDetail:UFlibAssetManageHelper::GetAssetDetailsByClass(AllAssetDetails,Class,RemoveFromSrc)) + { + ObjectPaths.Emplace(AssetDetail.PackagePath.ToString()); + } + return ObjectPaths; +} + +void UFlibAssetManageHelper::ReplaceReditector(TArray& SrcAssets) +{ + SCOPED_NAMED_EVENT_TEXT("ReplaceReditector",FColor::Red); + for(auto& AssetDetail:SrcAssets) + { + FName SrcPackagePath = AssetDetail.PackagePath; + + if(IsRedirector(AssetDetail,AssetDetail)) + { + UE_LOG(LogHotPatcher,Warning,TEXT("%s is an redirector(to %s)!"),*SrcPackagePath.ToString(),*AssetDetail.PackagePath.ToString()) + } + } +} + +void UFlibAssetManageHelper::RemoveInvalidAssets(TArray& SrcAssets) +{ + SCOPED_NAMED_EVENT_TEXT("RemoveInvalidAssets",FColor::Red); + for(size_t index = 0;index< SrcAssets.Num();) + { + FString PackageName = UFlibAssetManageHelper::PackagePathToLongPackageName(SrcAssets[index].PackagePath.ToString()); + if(FPackageName::DoesPackageExist(PackageName)) + { + ++index; + continue; + } + else + { + UE_LOG(LogHotPatcher,Warning,TEXT("%s is not a valid package."),*PackageName); + SrcAssets.RemoveAt(index); + } + } +} +#include "Misc/EngineVersionComparison.h" +FName UFlibAssetManageHelper::GetAssetDataClasses(const FAssetData& Data) +{ + if(Data.IsValid()) + { +#if !UE_VERSION_OLDER_THAN(5,1,0) + return Data.AssetClassPath.GetAssetName(); +#else + return Data.AssetClass; +#endif + } + return NAME_None; +} + +FName UFlibAssetManageHelper::GetObjectPathByAssetData(const FAssetData& Data) +{ + if(Data.IsValid()) + { +#if !UE_VERSION_OLDER_THAN(5,1,0) + return *UFlibAssetManageHelper::LongPackageNameToPackagePath(Data.PackageName.ToString()); +#else + return Data.ObjectPath; +#endif + } + 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}); + } +} +TArray UFlibAssetManageHelper::GetPackgeFiles(const FString& LongPackageName,const FString& Extension) +{ + SCOPED_NAMED_EVENT_TEXT("GetPackgeFiles",FColor::Red); + TArray Files; + + FString FilePath = FPackageName::LongPackageNameToFilename(LongPackageName) + Extension; + + if(FPaths::FileExists(FilePath)) + { + FPaths::MakeStandardFilename(FilePath); + Files.Add(FilePath); + } + return Files; +}; + +// PRAGMA_ENABLE_DEPRECATION_WARNINGS diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/FlibPakHelper.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/FlibPakHelper.cpp new file mode 100644 index 0000000..82be5b4 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Private/FlibPakHelper.cpp @@ -0,0 +1,561 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "FlibPakHelper.h" +#include "IPlatformFilePak.h" +#include "HAL/PlatformFilemanager.h" +#include "AssetManager/FFileArrayDirectoryVisitor.hpp" +#include "HotPatcherLog.h" +#include "FlibAssetManageHelper.h" +#include "HotPatcherTemplateHelper.hpp" +// Engine Header +#include "Resources/Version.h" +#include "Serialization/ArrayReader.h" +#include "AssetRegistryModule.h" +#include "IAssetRegistry.h" +#include "Misc/ScopeExit.h" +#include "Serialization/JsonSerializer.h" +#include "Serialization/JsonReader.h" +#include "Misc/FileHelper.h" +#include "IPlatformFilePak.h" +#include "ShaderPipelineCache.h" +#include "RHI.h" +#include "AssetRegistryState.h" +#include "Misc/Base64.h" +#include "Misc/CoreDelegates.h" +#include "Serialization/LargeMemoryReader.h" +#include "ShaderCodeLibrary.h" + +void UFlibPakHelper::ExecMountPak(FString InPakPath, int32 InPakOrder, FString InMountPoint) +{ + UFlibPakHelper::MountPak(InPakPath, InPakOrder, InMountPoint); +} + +bool UFlibPakHelper::MountPak(const FString& PakPath, int32 PakOrder, const FString& InMountPoint) +{ + bool bMounted = false; + + FPakPlatformFile* PakFileMgr = (FPakPlatformFile*)FPlatformFileManager::Get().GetPlatformFile(FPakPlatformFile::GetTypeName()); + if (!PakFileMgr) + { + UE_LOG(LogHotPatcher, Log, TEXT("GetPlatformFile(TEXT(\"PakFile\") is NULL")); + return false; + } + + PakOrder = FMath::Max(0, PakOrder); + + if (FPaths::FileExists(PakPath) && FPaths::GetExtension(PakPath) == TEXT("pak")) + { + bool bIsEmptyMountPoint = InMountPoint.IsEmpty(); + const TCHAR* MountPoint = bIsEmptyMountPoint ? NULL : InMountPoint.GetCharArray().GetData(); + +#if !WITH_EDITOR + + if (PakFileMgr->Mount(*PakPath, PakOrder, MountPoint)) + { + UE_LOG(LogHotPatcher, Log, TEXT("Mounted = %s, Order = %d, MountPoint = %s"), *PakPath, PakOrder, !MountPoint ? TEXT("(NULL)") : MountPoint); + bMounted = true; + } + else { + UE_LOG(LogHotPatcher, Error, TEXT("Faild to mount pak = %s"), *PakPath); + bMounted = false; + } + +#endif + } + + return bMounted; +} + +bool UFlibPakHelper::UnMountPak(const FString& PakPath) +{ + bool bMounted = false; + + FPakPlatformFile* PakFileMgr = (FPakPlatformFile*)FPlatformFileManager::Get().GetPlatformFile(FPakPlatformFile::GetTypeName()); + if (!PakFileMgr) + { + UE_LOG(LogHotPatcher, Log, TEXT("GetPlatformFile(TEXT(\"PakFile\") is NULL")); + return false; + } + + if (!FPaths::FileExists(PakPath)) + return false; + return PakFileMgr->Unmount(*PakPath); +} + +bool UFlibPakHelper::CreateFileByBytes(const FString& InFile, const TArray& InBytes, int32 InWriteFlag /*= 0*/) +{ + if (InFile.IsEmpty()/* && FPaths::FileExists(InFile)*/) + { + UE_LOG(LogHotPatcher, Log, TEXT("CreateFileByBytes InFile is Empty!")); + // UE_LOG(LogHotPatcher, Log, TEXT("CreateFileByBytes file %s existing!"), *InFile); + return false; + } + FArchive* SaveToFile = NULL; + SaveToFile = IFileManager::Get().CreateFileWriter(*InFile, InWriteFlag); + check(SaveToFile != nullptr); + SaveToFile->Serialize((void*)InBytes.GetData(), InBytes.Num()); + delete SaveToFile; + return true; +} + + +bool UFlibPakHelper::ScanPlatformDirectory(const FString& InRelativePath, bool bIncludeFile, bool bIncludeDir, bool bRecursively, TArray& OutResault) +{ + bool bRunStatus = false; + OutResault.Reset(); + + IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); + if(PlatformFile.DirectoryExists(*InRelativePath)) + { + TArray Files; + TArray Dirs; + + FFileArrayDirectoryVisitor FallArrayDirVisitor; + if (bRecursively) + { + PlatformFile.IterateDirectoryRecursively(*InRelativePath, FallArrayDirVisitor); + } + else + { + PlatformFile.IterateDirectory(*InRelativePath, FallArrayDirVisitor); + } + Files = FallArrayDirVisitor.Files; + Dirs = FallArrayDirVisitor.Directories; + + if (bIncludeFile) + { + OutResault = Files; + } + if (bIncludeDir) + { + OutResault.Append(Dirs); + } + + bRunStatus = true; + } + else + { + UE_LOG(LogHotPatcher, Warning, TEXT("Directory %s is not found."), *InRelativePath); + } + + return bRunStatus; +} + +bool UFlibPakHelper::ScanExtenFilesInDirectory(const FString& InRelativePath, const FString& InExtenPostfix, bool InRecursively, TArray& OutFiles) +{ + OutFiles.Reset(); + TArray ReceiveScanResult; + if (!UFlibPakHelper::ScanPlatformDirectory(InRelativePath, true, false, InRecursively, ReceiveScanResult)) + { + UE_LOG(LogHotPatcher, Warning, TEXT("UFlibPakHelper::ScanPlatformDirectory return false.")); + return false; + } + TArray FinalResult; + for (const auto& File : ReceiveScanResult) + { + if (File.EndsWith(*InExtenPostfix,ESearchCase::IgnoreCase)) + { + FinalResult.AddUnique(File); + } + } + OutFiles = FinalResult; + return true; +} + +TArray UFlibPakHelper::ScanAllVersionDescribleFiles() +{ + TArray FinalResult; + + FString VersionDescribleDir = FPaths::Combine(FPaths::ProjectContentDir(), TEXT("Extension/Versions")); + UFlibPakHelper::ScanExtenFilesInDirectory(VersionDescribleDir, TEXT(".json"), true, FinalResult); + + return FinalResult; +} + +TArray UFlibPakHelper::ScanExtenPakFiles() +{ + TArray FinalResult; + + FString ExtenPakDir = FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("ExtenPak")); + UFlibPakHelper::ScanExtenFilesInDirectory(ExtenPakDir, TEXT(".pak"), true, FinalResult); + + return FinalResult; +} + +int32 UFlibPakHelper::GetPakOrderByPakPath(const FString& PakFile) +{ + int32 PakOrder = 0; + if (!PakFile.IsEmpty()) + { + FString PakFilename = PakFile; + if (PakFilename.EndsWith(TEXT("_P.pak"))) + { + // Prioritize based on the chunk version number + // Default to version 1 for single patch system + uint32 ChunkVersionNumber = 1; + FString StrippedPakFilename = PakFilename.LeftChop(6); + int32 VersionEndIndex = PakFilename.Find("_", ESearchCase::CaseSensitive, ESearchDir::FromEnd); + if (VersionEndIndex != INDEX_NONE && VersionEndIndex > 0) + { + int32 VersionStartIndex = PakFilename.Find("_", ESearchCase::CaseSensitive, ESearchDir::FromEnd, VersionEndIndex - 1); + if (VersionStartIndex != INDEX_NONE) + { + VersionStartIndex++; + FString VersionString = PakFilename.Mid(VersionStartIndex, VersionEndIndex - VersionStartIndex); + if (VersionString.IsNumeric()) + { + int32 ChunkVersionSigned = FCString::Atoi(*VersionString); + if (ChunkVersionSigned >= 1) + { + // Increment by one so that the first patch file still gets more priority than the base pak file + ChunkVersionNumber = (uint32)ChunkVersionSigned + 1; + } + } + } + } + PakOrder += 100 * ChunkVersionNumber; + } + } + return PakOrder; +} + +void UFlibPakHelper::ReloadShaderbytecode() +{ + FShaderCodeLibrary::OpenLibrary("Global", FPaths::ProjectContentDir()); + FShaderCodeLibrary::OpenLibrary(FApp::GetProjectName(), FPaths::ProjectContentDir()); +} + + +bool UFlibPakHelper::LoadShaderbytecode(const FString& LibraryName, const FString& LibraryDir,bool bNative) +{ + bool result = true; + FString FinalLibraryDir = LibraryDir; +#if PLATFORM_IOS + if(bNative) + { + FinalLibraryDir = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*LibraryDir); + } +#endif +#if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION >= 23 + result = FShaderCodeLibrary::OpenLibrary(LibraryName, LibraryDir); +#else + FShaderCodeLibrary::OpenLibrary(LibraryName, LibraryDir); +#endif + UE_LOG(LogHotPatcher,Log,TEXT("Load Shader bytecode %s,Dir: %s, status: %s"),*LibraryName,*FinalLibraryDir,result?TEXT("True"):TEXT("False")); + return result; +} + +bool UFlibPakHelper::LoadShaderbytecodeInDefaultDir(const FString& LibraryName) +{ + return LoadShaderbytecode(LibraryName,FPaths::Combine(FPaths::ProjectDir(),TEXT("ShaderLibs"))); +} + +void UFlibPakHelper::CloseShaderbytecode(const FString& LibraryName) +{ + FShaderCodeLibrary::CloseLibrary(LibraryName); +} + +#if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 26 +bool UFlibPakHelper::LoadAssetRegistryToState(const TCHAR* Path,FAssetRegistryState& Out) +{ + check(Path); + + TUniquePtr FileReader(IFileManager::Get().CreateFileReader(Path)); + if (FileReader) + { + // It's faster to load the whole file into memory on a Gen5 console + TArray64 Data; + Data.SetNumUninitialized(FileReader->TotalSize()); + FileReader->Serialize(Data.GetData(), Data.Num()); + check(!FileReader->IsError()); + + FLargeMemoryReader MemoryReader(Data.GetData(), Data.Num()); + return Out.Load(MemoryReader, FAssetRegistryLoadOptions{}); + } + + return false; +} +#else +bool UFlibPakHelper::LoadAssetRegistryToState(const TCHAR* Path, FAssetRegistryState& Out) +{ + bool bStatus = false; + FString AssetRegistryPath = Path; + FArrayReader SerializedAssetData; + // FString AssetRegistryBinPath = InAssetRegistryBin; + // UE_LOG(LogHotPatcher,Log,TEXT("Load AssetRegistry %s"),*AssetRegistryBinPath); + if (IFileManager::Get().FileExists(*AssetRegistryPath) && FFileHelper::LoadFileToArray(SerializedAssetData, *AssetRegistryPath)) + { + FAssetRegistryState State; + FAssetRegistrySerializationOptions SerializationOptions; + SerializationOptions.bSerializeAssetRegistry = true; + bStatus = State.Serialize(SerializedAssetData, SerializationOptions); + } + return bStatus; +} +#endif + + +bool UFlibPakHelper::LoadAssetRegistry(const FString& LibraryName, const FString& LibraryDir) +{ + bool bStatus = false; + FString AssetRegistryPath = FPaths::Combine(LibraryDir,FString::Printf(TEXT("%s.bin"),*LibraryName)); + UE_LOG(LogHotPatcher,Log,TEXT("Load Asset Registry %s"),*AssetRegistryPath); + if(FPaths::FileExists(AssetRegistryPath)) + { + FAssetRegistryState State; + if(LoadAssetRegistryToState(*AssetRegistryPath,State)) + { + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); +#if ENGINE_MAJOR_VERSION > 4 || (ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION > 21) + AssetRegistryModule.Get().AppendState(State); +#else + // todo support append state in 4.21 +#endif + bStatus = true; + } + } + return bStatus; +} + + +#include "Misc/EngineVersionComparison.h" +bool UFlibPakHelper::OpenPSO(const FString& Name) +{ +#if UE_VERSION_OLDER_THAN(5,1,0) + return FShaderPipelineCache::OpenPipelineFileCache(Name,GMaxRHIShaderPlatform); +#else + return FShaderPipelineCache::OpenPipelineFileCache(GMaxRHIShaderPlatform); +#endif +} + +FAES::FAESKey CachedAESKey; +#define AES_KEY_SIZE 32 +bool ValidateEncryptionKey(TArray& IndexData, const FSHAHash& InExpectedHash, const FAES::FAESKey& InAESKey) +{ + FAES::DecryptData(IndexData.GetData(), IndexData.Num(), InAESKey); + + // Check SHA1 value. + FSHAHash ActualHash; + FSHA1::HashBuffer(IndexData.GetData(), IndexData.Num(), ActualHash.Hash); + return InExpectedHash == ActualHash; +} + +bool PreLoadPak(const FString& InPakPath,const FString& AesKey) +{ + UE_LOG(LogHotPatcher, Log, TEXT("Pre load pak file: %s and check file hash."), *InPakPath); + + FArchive* Reader = IFileManager::Get().CreateFileReader(*InPakPath); + if (!Reader) + { + return false; + } + + FPakInfo Info; + const int64 CachedTotalSize = Reader->TotalSize(); + bool bShouldLoad = false; + int32 CompatibleVersion = FPakInfo::PakFile_Version_Latest; + + // Serialize trailer and check if everything is as expected. + // start up one to offset the -- below + CompatibleVersion++; + int64 FileInfoPos = -1; + do + { + // try the next version down + CompatibleVersion--; + + FileInfoPos = CachedTotalSize - Info.GetSerializedSize(CompatibleVersion); + if (FileInfoPos >= 0) + { + Reader->Seek(FileInfoPos); + + // Serialize trailer and check if everything is as expected. + Info.Serialize(*Reader, CompatibleVersion); + if (Info.Magic == FPakInfo::PakFile_Magic) + { + bShouldLoad = true; + } + } + } while (!bShouldLoad && CompatibleVersion >= FPakInfo::PakFile_Version_Initial); + + if (!bShouldLoad) + { + Reader->Close(); + delete Reader; + return false; + } + + if (Info.EncryptionKeyGuid.IsValid() || Info.bEncryptedIndex) + { + const FString KeyString = AesKey; + + FAES::FAESKey AESKey; + + TArray DecodedBuffer; + if (!FBase64::Decode(KeyString, DecodedBuffer)) + { + UE_LOG(LogHotPatcher, Error, TEXT("AES encryption key base64[%s] is not base64 format!"), *KeyString); + bShouldLoad = false; + } + + // Error checking + if (bShouldLoad && DecodedBuffer.Num() != AES_KEY_SIZE) + { + UE_LOG(LogHotPatcher, Error, TEXT("AES encryption key base64[%s] can not decode to %d bytes long!"), *KeyString, AES_KEY_SIZE); + bShouldLoad = false; + } + + if (bShouldLoad) + { + FMemory::Memcpy(AESKey.Key, DecodedBuffer.GetData(), AES_KEY_SIZE); + + TArray PrimaryIndexData; + Reader->Seek(Info.IndexOffset); + PrimaryIndexData.SetNum(Info.IndexSize); + Reader->Serialize(PrimaryIndexData.GetData(), Info.IndexSize); + + FSHAHash Hash; + FMemory::Memcpy(Hash.Hash, +#if ENGINE_MAJOR_VERSION > 4 || (ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 23) + Info.IndexHash.Hash, +#else + Info.IndexHash, +#endif + 20); + + if (!ValidateEncryptionKey(PrimaryIndexData, Hash, AESKey)) + { + UE_LOG(LogHotPatcher, Error, TEXT("AES encryption key base64[%s] is not correct!"), *KeyString); + bShouldLoad = false; + } + else + { + CachedAESKey = AESKey; + + UE_LOG(LogHotPatcher, Log, TEXT("Use AES encryption key base64[%s] for %s."), *KeyString, *InPakPath); + FCoreDelegates::GetPakEncryptionKeyDelegate().BindLambda( + [AESKey](uint8 OutKey[32]) + { + FMemory::Memcpy(OutKey, AESKey.Key, 32); + }); + + if (Info.EncryptionKeyGuid.IsValid()) + { +#if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION >= 26 + FCoreDelegates::GetRegisterEncryptionKeyMulticastDelegate().Broadcast(Info.EncryptionKeyGuid, AESKey); +#else + FCoreDelegates::GetRegisterEncryptionKeyDelegate().ExecuteIfBound(Info.EncryptionKeyGuid, AESKey); +#endif + } + } + } + } + + Reader->Close(); + delete Reader; + return bShouldLoad; +} +#if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 25 +#define FFileIterator FFilenameIterator +#endif + +FSHA1 UFlibPakHelper::GetPakEntryHASH(FPakFile* InPakFile,const FPakEntry& PakEntry) +{ + FSHA1 Sha1; + auto Reader = InPakFile->GetSharedReader(nullptr); + Reader->Seek(PakEntry.Offset); + FPakEntry SerializedEntry; +#if ENGINE_MAJOR_VERSION > 4 + SerializedEntry.Serialize(Reader.GetArchive(), InPakFile->GetInfo().Version); +#else + SerializedEntry.Serialize(*Reader, InPakFile->GetInfo().Version); +#endif + FMemory::Memcpy(Sha1.m_digest, &SerializedEntry.Hash, sizeof(SerializedEntry.Hash)); + return Sha1; +} + +TArray UFlibPakHelper::GetPakFileList(const FString& InPak, const FString& AESKey) +{ + TArray Records; + + auto PakFilePtr = UFlibPakHelper::GetPakFileIns(InPak,AESKey);; +#if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 26 + TRefCountPtr PakFile = PakFilePtr; +#else + TSharedPtr PakFile = MakeShareable(PakFilePtr); +#endif + UFlibPakHelper::GetPakEntrys(PakFilePtr).GetKeys(Records); + return Records; +} + +TMap UFlibPakHelper::GetPakEntrys(FPakFile* InPakFile) +{ + TMap Records; + + if(InPakFile) + { + FString MountPoint = InPakFile->GetMountPoint(); + for (FPakFile::FFileIterator It(*InPakFile, true); It; ++It) + { + const FString& Filename = It.Filename(); + FPakEntry PakEntry; + PakEntry = It.Info(); + FSHA1 Sha1 = GetPakEntryHASH(InPakFile,PakEntry); + FMemory::Memcpy(PakEntry.Hash,Sha1.m_digest,sizeof(Sha1.m_digest)); + Records.Emplace(MountPoint + Filename,PakEntry); + } + + } + return Records; +} + +FPakFile* UFlibPakHelper::GetPakFileIns(const FString& InPak, const FString& AESKey) +{ + IPlatformFile* PlatformIns = &FPlatformFileManager::Get().GetPlatformFile(); + FPakFile* rPakFile = nullptr; + FString StandardFileName = InPak; + FPaths::MakeStandardFilename(StandardFileName); + TArray Records; + if(PreLoadPak(StandardFileName,AESKey)) + { +// #if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 26 +// TRefCountPtr PakFile = new FPakFile(&PlatformIns->GetPlatformPhysical(), *StandardFileName, false); +// rPakFile = PakFile.GetReference(); +// #else + rPakFile = new FPakFile(&PlatformIns->GetPlatformPhysical(), *StandardFileName, false,true); +//#endif + } + return rPakFile; +} + +FString UFlibPakHelper::GetPakFileMountPoint(const FString& InPak, const FString& AESKey) +{ + FString result; + + auto PakFilePtr = UFlibPakHelper::GetPakFileIns(InPak,AESKey);; +#if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 26 + TRefCountPtr PakFile = PakFilePtr; +#else + TSharedPtr PakFile = MakeShareable(PakFilePtr); +#endif + + if(PakFilePtr) + { + result = PakFilePtr->GetMountPoint(); + } + return result; +} + +TArray UFlibPakHelper::GetAllMountedPaks() +{ + FPakPlatformFile* PakPlatformFile = (FPakPlatformFile*)(FPlatformFileManager::Get().FindPlatformFile(FPakPlatformFile::GetTypeName())); + + TArray Resault; + if(PakPlatformFile) + PakPlatformFile->GetMountedPakFilenames(Resault); + else + UE_LOG(LogHotPatcher,Warning,TEXT("PakPlatformFile is invalid!")); + return Resault; +} + diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/FlibPakReader.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/FlibPakReader.cpp new file mode 100644 index 0000000..b346e81 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Private/FlibPakReader.cpp @@ -0,0 +1,445 @@ +// // Fill out your copyright notice in the Description page of Project Settings. +// +// +// #include "FlibPakReader.h" +// +// #include "Misc/FileHelper.h" +// #include "IPlatformFilePak.h" +// #include "HAL/PlatformFilemanager.h" +// #include "Resources/Version.h" +// +// #if PLATFORM_WINDOWS +// /** +// * Thread local class to manage working buffers for file compression +// */ +// class FCompressionScratchBuffers : public TThreadSingleton +// { +// public: +// FCompressionScratchBuffers() +// : TempBufferSize(0) +// , ScratchBufferSize(0) +// , LastReader(nullptr) +// , LastDecompressedBlock(0xFFFFFFFF) +// {} +// +// int64 TempBufferSize; +// TUniquePtr TempBuffer; +// int64 ScratchBufferSize; +// TUniquePtr ScratchBuffer; +// +// void* LastReader; +// uint32 LastDecompressedBlock; +// +// void EnsureBufferSpace(int64 CompressionBlockSize, int64 ScrachSize) +// { +// if (TempBufferSize < CompressionBlockSize) +// { +// TempBufferSize = CompressionBlockSize; +// TempBuffer = MakeUnique(TempBufferSize); +// } +// if (ScratchBufferSize < ScrachSize) +// { +// ScratchBufferSize = ScrachSize; +// ScratchBuffer = MakeUnique(ScratchBufferSize); +// } +// } +// +// }; +// /** +// * Class to handle correctly reading from a compressed file within a pak +// */ +// template< typename EncryptionPolicy = FPakNoEncryption > +// class FPakCompressedReaderPolicy +// { +// public: +// class FPakUncompressTask : public FNonAbandonableTask +// { +// public: +// uint8* UncompressedBuffer; +// int32 UncompressedSize; +// uint8* CompressedBuffer; +// int32 CompressedSize; +// FName CompressionFormat; +// void* CopyOut; +// int64 CopyOffset; +// int64 CopyLength; +// FGuid EncryptionKeyGuid; +// +// void DoWork() +// { +// // Decrypt and Uncompress from memory to memory. +// int64 EncryptionSize = EncryptionPolicy::AlignReadRequest(CompressedSize); +// EncryptionPolicy::DecryptBlock(CompressedBuffer, EncryptionSize, EncryptionKeyGuid); +// FCompression::UncompressMemory(CompressionFormat, UncompressedBuffer, UncompressedSize, CompressedBuffer, CompressedSize); +// if (CopyOut) +// { +// FMemory::Memcpy(CopyOut, UncompressedBuffer + CopyOffset, CopyLength); +// } +// } +// +// FORCEINLINE TStatId GetStatId() const +// { +// // TODO: This is called too early in engine startup. +// return TStatId(); +// //RETURN_QUICK_DECLARE_CYCLE_STAT(FPakUncompressTask, STATGROUP_ThreadPoolAsyncTasks); +// } +// }; +// +// FPakCompressedReaderPolicy(const FPakFile& InPakFile, const FPakEntry& InPakEntry, TAcquirePakReaderFunction& InAcquirePakReader) +// : PakFile(InPakFile) +// , PakEntry(InPakEntry) +// , AcquirePakReader(InAcquirePakReader) +// { +// } +// +// ~FPakCompressedReaderPolicy() +// { +// FCompressionScratchBuffers& ScratchSpace = FCompressionScratchBuffers::Get(); +// if(ScratchSpace.LastReader == this) +// { +// ScratchSpace.LastDecompressedBlock = 0xFFFFFFFF; +// ScratchSpace.LastReader = nullptr; +// } +// } +// +// /** Pak file that own this file data */ +// const FPakFile& PakFile; +// /** Pak file entry for this file. */ +// FPakEntry PakEntry; +// /** Function that gives us an FArchive to read from. The result should never be cached, but acquired and used within the function doing the serialization operation */ +// TAcquirePakReaderFunction AcquirePakReader; +// +// FORCEINLINE int64 FileSize() const +// { +// return PakEntry.UncompressedSize; +// } +// +// void Serialize(int64 DesiredPosition, void* V, int64 Length) +// { +// const int32 CompressionBlockSize = PakEntry.CompressionBlockSize; +// uint32 CompressionBlockIndex = DesiredPosition / CompressionBlockSize; +// uint8* WorkingBuffers[2]; +// int64 DirectCopyStart = DesiredPosition % PakEntry.CompressionBlockSize; +// FAsyncTask UncompressTask; +// FCompressionScratchBuffers& ScratchSpace = FCompressionScratchBuffers::Get(); +// bool bStartedUncompress = false; +// +// FName CompressionMethod = PakFile.GetInfo().GetCompressionMethod(PakEntry.CompressionMethodIndex); +// checkf(FCompression::IsFormatValid(CompressionMethod), +// TEXT("Attempting to use compression format %s when loading a file from a .pak, but that compression format is not available.\n") +// TEXT("If you are running a program (like UnrealPak) you may need to pass the .uproject on the commandline so the plugin can be found.\n"), +// TEXT("It's also possible that a necessary compression plugin has not been loaded yet, and this file needs to be forced to use zlib compression.\n") +// TEXT("Unfortunately, the code that can check this does not have the context of the filename that is being read. You will need to look in the callstack in a debugger.\n") +// TEXT("See ExtensionsToNotUsePluginCompression in [Pak] section of Engine.ini to add more extensions."), +// *CompressionMethod.ToString(), TEXT("Unknown")); +// +// // an amount to extra allocate, in case one block's compressed size is bigger than CompressMemoryBound +// float SlopMultiplier = 1.1f; +// int64 WorkingBufferRequiredSize = FCompression::CompressMemoryBound(CompressionMethod, CompressionBlockSize) * SlopMultiplier; +// WorkingBufferRequiredSize = EncryptionPolicy::AlignReadRequest(WorkingBufferRequiredSize); +// const bool bExistingScratchBufferValid = ScratchSpace.TempBufferSize >= CompressionBlockSize; +// ScratchSpace.EnsureBufferSpace(CompressionBlockSize, WorkingBufferRequiredSize * 2); +// WorkingBuffers[0] = ScratchSpace.ScratchBuffer.Get(); +// WorkingBuffers[1] = ScratchSpace.ScratchBuffer.Get() + WorkingBufferRequiredSize; +// +// FArchive* PakReader = AcquirePakReader(); +// +// while (Length > 0) +// { +// const FPakCompressedBlock& Block = PakEntry.CompressionBlocks[CompressionBlockIndex]; +// int64 Pos = CompressionBlockIndex * CompressionBlockSize; +// int64 CompressedBlockSize = Block.CompressedEnd - Block.CompressedStart; +// int64 UncompressedBlockSize = FMath::Min(PakEntry.UncompressedSize - Pos, PakEntry.CompressionBlockSize); +// +// if (CompressedBlockSize > UncompressedBlockSize) +// { +// UE_LOG(LogPakFile, Display, TEXT("Bigger compressed? Block[%d]: %d -> %d > %d [%d min %d]"), CompressionBlockIndex, Block.CompressedStart, Block.CompressedEnd, UncompressedBlockSize, PakEntry.UncompressedSize - Pos, PakEntry.CompressionBlockSize); +// } +// +// +// int64 ReadSize = EncryptionPolicy::AlignReadRequest(CompressedBlockSize); +// int64 WriteSize = FMath::Min(UncompressedBlockSize - DirectCopyStart, Length); +// +// const bool bCurrentScratchTempBufferValid = +// bExistingScratchBufferValid && !bStartedUncompress +// // ensure this object was the last reader from the scratch buffer and the last thing it decompressed was this block. +// && ScratchSpace.LastReader == this && ScratchSpace.LastDecompressedBlock == CompressionBlockIndex +// // ensure the previous decompression destination was the scratch buffer. +// && !(DirectCopyStart == 0 && Length >= CompressionBlockSize); +// +// if (bCurrentScratchTempBufferValid) +// { +// // Reuse the existing scratch buffer to avoid repeatedly deserializing and decompressing the same block. +// FMemory::Memcpy(V, ScratchSpace.TempBuffer.Get() + DirectCopyStart, WriteSize); +// } +// else +// { +// PakReader->Seek(Block.CompressedStart + (PakFile.GetInfo().HasRelativeCompressedChunkOffsets() ? PakEntry.Offset : 0)); +// PakReader->Serialize(WorkingBuffers[CompressionBlockIndex & 1], ReadSize); +// if (bStartedUncompress) +// { +// UncompressTask.EnsureCompletion(); +// bStartedUncompress = false; +// } +// +// FPakUncompressTask& TaskDetails = UncompressTask.GetTask(); +// TaskDetails.EncryptionKeyGuid = PakFile.GetInfo().EncryptionKeyGuid; +// +// if (DirectCopyStart == 0 && Length >= CompressionBlockSize) +// { +// // Block can be decompressed directly into output buffer +// TaskDetails.CompressionFormat = CompressionMethod; +// TaskDetails.UncompressedBuffer = (uint8*)V; +// TaskDetails.UncompressedSize = UncompressedBlockSize; +// TaskDetails.CompressedBuffer = WorkingBuffers[CompressionBlockIndex & 1]; +// TaskDetails.CompressedSize = CompressedBlockSize; +// TaskDetails.CopyOut = nullptr; +// ScratchSpace.LastDecompressedBlock = 0xFFFFFFFF; +// ScratchSpace.LastReader = nullptr; +// } +// else +// { +// // Block needs to be copied from a working buffer +// TaskDetails.CompressionFormat = CompressionMethod; +// TaskDetails.UncompressedBuffer = ScratchSpace.TempBuffer.Get(); +// TaskDetails.UncompressedSize = UncompressedBlockSize; +// TaskDetails.CompressedBuffer = WorkingBuffers[CompressionBlockIndex & 1]; +// TaskDetails.CompressedSize = CompressedBlockSize; +// TaskDetails.CopyOut = V; +// TaskDetails.CopyOffset = DirectCopyStart; +// TaskDetails.CopyLength = WriteSize; +// +// ScratchSpace.LastDecompressedBlock = CompressionBlockIndex; +// ScratchSpace.LastReader = this; +// } +// +// if (Length == WriteSize) +// { +// UncompressTask.StartSynchronousTask(); +// } +// else +// { +// UncompressTask.StartBackgroundTask(); +// } +// bStartedUncompress = true; +// } +// +// V = (void*)((uint8*)V + WriteSize); +// Length -= WriteSize; +// DirectCopyStart = 0; +// ++CompressionBlockIndex; +// } +// +// if (bStartedUncompress) +// { +// UncompressTask.EnsureCompletion(); +// } +// } +// }; +// +// void DecryptDataEx(uint8* InData, uint32 InDataSize, FGuid InEncryptionKeyGuid) +// { +// #if ENGINE_MINOR_VERSION >22 +// if (FPakPlatformFile::GetPakCustomEncryptionDelegate().IsBound()) +// { +// FPakPlatformFile::GetPakCustomEncryptionDelegate().Execute(InData, InDataSize, InEncryptionKeyGuid); +// } +// else +// #endif +// { +// // SCOPE_SECONDS_ACCUMULATOR(STAT_PakCache_DecryptTime); +// FAES::FAESKey Key; +// FPakPlatformFile::GetPakEncryptionKey(Key, InEncryptionKeyGuid); +// check(Key.IsValid()); +// FAES::DecryptData(InData, InDataSize, Key); +// } +// } +// +// /** +// * Class to handle correctly reading from a compressed file within a compressed package +// */ +// class FPakSimpleEncryption +// { +// public: +// enum +// { +// Alignment = FAES::AESBlockSize, +// }; +// +// static FORCEINLINE int64 AlignReadRequest(int64 Size) +// { +// return Align(Size, Alignment); +// } +// +// static FORCEINLINE void DecryptBlock(void* Data, int64 Size, const FGuid& EncryptionKeyGuid) +// { +// // INC_DWORD_STAT(STAT_PakCache_SyncDecrypts); +// DecryptDataEx((uint8*)Data, Size, EncryptionKeyGuid); +// } +// }; +// +// +// FPakFile* UFlibPakReader::GetPakFileInsByPath(const FString& PakPath) +// { +// FPakFile* Pak = NULL; +// #if !WITH_EDITOR +// FPakPlatformFile* PakFileMgr = (FPakPlatformFile*)FPlatformFileManager::Get().GetPlatformFile(FPakPlatformFile::GetTypeName()); +// IPlatformFile* LowerLevel = PakFileMgr->GetLowerLevel(); +// if(LowerLevel) +// { +// #if ENGINE_MINOR_VERSION > 23 +// Pak = new FPakFile(LowerLevel, *PakPath, false, true); +// #else +// Pak = new FPakFile(LowerLevel, *PakPath, false); +// #endif +// } +// #endif +// return Pak; +// } +// +// TArray UFlibPakReader::GetPakFileList(const FString& PakFilePath) +// { +// TArray Files; +// FPakFile* Pak = UFlibPakReader::GetPakFileInsByPath(PakFilePath); +// if(Pak) +// { +// #if ENGINE_MINOR_VERSION >22 +// Pak->GetFilenames(Files); +// #endif +// } +// return Files; +// } +// +// bool UFlibPakReader::FindFileInPakFile(FPakFile* InPakFile, const FString& InFileName, FPakEntry* OutPakEntry) +// { +// bool result = false; +// if(InPakFile) +// { +// FPakEntry FileEntry; +// FPakFile::EFindResult FindResult = InPakFile->Find(InFileName,&FileEntry); +// if(FPakFile::EFindResult::Found == FindResult) +// { +// *OutPakEntry = FileEntry; +// result = true; +// } +// } +// return result; +// } +// +// IFileHandle* UFlibPakReader::CreatePakFileHandle(IPlatformFile* InLowLevel, FPakFile* PakFile, const FPakEntry* FileEntry) +// { +// IFileHandle* Result = NULL; +// bool bNeedsDelete = true; +// TFunction AcquirePakReader = [PakFile, LowerLevelPlatformFile = InLowLevel]() { return PakFile->GetSharedReader(LowerLevelPlatformFile); }; +// +// // Create the handle. +// if (FileEntry->CompressionMethodIndex != 0 && PakFile->GetInfo().Version >= FPakInfo::PakFile_Version_CompressionEncryption) +// { +// if (FileEntry->IsEncrypted()) +// { +// Result = new FPakFileHandle< FPakCompressedReaderPolicy >(*PakFile, *FileEntry, AcquirePakReader, bNeedsDelete); +// } +// else +// { +// Result = new FPakFileHandle< FPakCompressedReaderPolicy<> >(*PakFile, *FileEntry, AcquirePakReader, bNeedsDelete); +// } +// } +// else if (FileEntry->IsEncrypted()) +// { +// Result = new FPakFileHandle< FPakReaderPolicy >(*PakFile, *FileEntry, AcquirePakReader, bNeedsDelete); +// } +// else +// { +// Result = new FPakFileHandle<>(*PakFile, *FileEntry, AcquirePakReader, bNeedsDelete); +// } +// +// return Result; +// } +// +// /** +// * Load a text file to an FString. +// * Supports all combination of ANSI/Unicode files and platforms. +// * @param Result string representation of the loaded file +// * @param Filename name of the file to load +// * @param VerifyFlags flags controlling the hash verification behavior ( see EHashOptions ) +// */ +// bool UFlibPakReader::LoadFileToString(FString& Result, FArchive* InReader, const TCHAR* Filename, FFileHelper::EHashOptions VerifyFlags) +// { +// TUniquePtr Reader( InReader); +// if( !Reader ) +// { +// return false; +// } +// +// int32 Size = Reader->TotalSize(); +// if( !Size ) +// { +// Result.Empty(); +// return true; +// } +// +// uint8* Ch = (uint8*)FMemory::Malloc(Size); +// Reader->Serialize( Ch, Size ); +// bool Success = Reader->Close(); +// Reader = nullptr; +// FFileHelper::BufferToString( Result, Ch, Size ); +// +// // handle SHA verify of the file +// if( EnumHasAnyFlags(VerifyFlags, FFileHelper::EHashOptions::EnableVerify) && ( EnumHasAnyFlags(VerifyFlags, FFileHelper::EHashOptions::ErrorMissingHash) || FSHA1::GetFileSHAHash(Filename, NULL) ) ) +// { +// // kick off SHA verify task. this frees the buffer on close +// FBufferReaderWithSHA Ar( Ch, Size, true, Filename, false, true ); +// } +// else +// { +// // free manually since not running SHA task +// FMemory::Free(Ch); +// } +// +// return Success; +// +// } +// +// +// +// #include "HACK_PRIVATE_MEMBER_UTILS.hpp" +// DECL_HACK_PRIVATE_NOCONST_FUNCTION(FPakFile,CreatePakReader,FArchive*,IFileHandle&,const TCHAR*); +// +// FArchive* UFlibPakReader::CreatePakReader(FPakFile* InPakFile, IFileHandle& InHandle, const TCHAR* InFilename) +// { +// auto FPakFile_CreatePakReader =GET_PRIVATE_MEMBER_FUNCTION(FPakFile, CreatePakReader); +// return CALL_MEMBER_FUNCTION(InPakFile, FPakFile_CreatePakReader, InHandle,InFilename); +// } +// +// FString UFlibPakReader::LoadPakFileToString(const FString& InPakFile, const FString& InFileName) +// { +// FString ret; +// if(FPaths::FileExists(InPakFile)) +// { +// FPakFile* PakFile = UFlibPakReader::GetPakFileInsByPath(InPakFile); +// if(!PakFile) +// return ret; +// FPakEntry PakEntry; +// if(UFlibPakReader::FindFileInPakFile(PakFile,InFileName,&PakEntry)) +// { +// FPakPlatformFile* PakFileMgr = (FPakPlatformFile*)FPlatformFileManager::Get().GetPlatformFile(FPakPlatformFile::GetTypeName()); +// IPlatformFile* LowerLevel = PakFileMgr->GetLowerLevel(); +// if(LowerLevel) +// { +// IFileHandle* FileHandle = UFlibPakReader::CreatePakFileHandle(LowerLevel,PakFile,&PakEntry); +// if(FileHandle) +// { +// TUniquePtr Reader(UFlibPakReader::CreatePakReader(PakFile,*FileHandle, *InFileName)); +// #if ENGINE_MINOR_VERSION > 25 +// FFileHelper::LoadFileToString(ret,*Reader.Get()); +// #else +// UFlibPakReader::LoadFileToString(ret,Reader.Get(),*InFileName); +// #endif +// } +// } +// } +// } +// return ret; +// } +// +// #endif \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/FlibPatchParserHelper.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/FlibPatchParserHelper.cpp new file mode 100644 index 0000000..59f6412 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Private/FlibPatchParserHelper.cpp @@ -0,0 +1,2288 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +// project header +#include "FlibPatchParserHelper.h" +#include "FlibAssetManageHelper.h" +#include "BaseTypes/AssetManager/FFileArrayDirectoryVisitor.hpp" +#include "FlibAssetManageHelper.h" +#include "FlibAssetManageHelper.h" +#include "HotPatcherLog.h" +#include "CreatePatch/FExportPatchSettings.h" +#include "DependenciesParser/FDefaultAssetDependenciesParser.h" + +// engine header +#include "Misc/App.h" +#include "Misc/SecureHash.h" +#include "Kismet/KismetSystemLibrary.h" +#include "Kismet/KismetStringLibrary.h" +#include "Interfaces/IPluginManager.h" +#include "Serialization/JsonSerializer.h" +#include "HAL/FileManager.h" +#include "Engine/EngineTypes.h" +#include "JsonObjectConverter.h" +#include "AssetRegistryState.h" +#include "FPackageTracker.h" +#include "HotPatcherRuntime.h" +#include "Async/ParallelFor.h" +#include "CreatePatch/TimeRecorder.h" +#include "Misc/Paths.h" +#include "Kismet/KismetStringLibrary.h" +#include "Misc/Base64.h" +#include "Misc/FileHelper.h" +#include "Serialization/LargeMemoryReader.h" + +TArray UFlibPatchParserHelper::GetAvailableMaps(FString GameName, bool IncludeEngineMaps, bool IncludePluginMaps, bool Sorted) +{ + const FString WildCard = FString::Printf(TEXT("*%s"), *FPackageName::GetMapPackageExtension()); + TArray AllMaps; + TMap AllEnableModules; + + UFlibAssetManageHelper::GetAllEnabledModuleName(AllEnableModules); + + auto ScanMapsByModuleName = [WildCard,&AllMaps](const FString& InModuleBaseDir) + { + TArray OutMaps; + FString ModuleContentAbsPath= FPaths::ConvertRelativePathToFull(FPaths::Combine(*InModuleBaseDir, TEXT("Content"))); + IFileManager::Get().FindFilesRecursive(OutMaps, *ModuleContentAbsPath, *WildCard, true, false); + + for (const auto& MapPath : OutMaps) + { + AllMaps.Add(FPaths::GetBaseFilename(MapPath)); + } + }; + + ScanMapsByModuleName(AllEnableModules[TEXT("Game")]); + if (IncludeEngineMaps) + { + ScanMapsByModuleName(AllEnableModules[TEXT("Engine")]); + } + if (IncludePluginMaps) + { + TArray AllModuleNames; + AllEnableModules.GetKeys(AllModuleNames); + + for (const auto& ModuleName : AllModuleNames) + { + if (!ModuleName.Equals(TEXT("Game")) && !ModuleName.Equals(TEXT("Engine"))) + { + ScanMapsByModuleName(AllEnableModules[ModuleName]); + } + } + } + if (Sorted) + { + AllMaps.Sort(); + } + + return AllMaps; +} + +FString UFlibPatchParserHelper::GetProjectName() +{ + return FApp::GetProjectName(); +} + + +FString UFlibPatchParserHelper::GetProjectFilePath() +{ + FString ProjectFilePath; + { + FString ProjectPath = UKismetSystemLibrary::GetProjectDirectory(); + FString ProjectName = FString(FApp::GetProjectName()).Append(TEXT(".uproject")); + ProjectFilePath = FPaths::Combine(ProjectPath, ProjectName); + } + return ProjectFilePath; +} + + +bool UFlibPatchParserHelper::DiffVersionAssets( + const FAssetDependenciesInfo& InNewVersion, + const FAssetDependenciesInfo& InBaseVersion, + FAssetDependenciesInfo& OutAddAsset, + FAssetDependenciesInfo& OutModifyAsset, + FAssetDependenciesInfo& OutDeleteAsset +) +{ + FAssetDependenciesInfo result; + TArray AddAsset; + TArray ModifyAsset; + TArray DeleteAsset; + + { + TArray InNewAssetModuleKeyList; + InNewVersion.AssetsDependenciesMap.GetKeys(InNewAssetModuleKeyList); + + TArray InBaseAssetModuleKeysList; + InBaseVersion.AssetsDependenciesMap.GetKeys(InBaseAssetModuleKeysList); + + // Parser Add new asset + for (const auto& NewVersionAssetModule : InNewAssetModuleKeyList) + { + // is a new mdoule? + if (!InBaseAssetModuleKeysList.Contains(NewVersionAssetModule)) + { + OutAddAsset.AssetsDependenciesMap.Add(NewVersionAssetModule, *InNewVersion.AssetsDependenciesMap.Find(NewVersionAssetModule)); + continue; + } + { + TArray NewVersionDependAssetsList; + InNewVersion.AssetsDependenciesMap.Find(NewVersionAssetModule)->AssetDependencyDetails.GetKeys(NewVersionDependAssetsList); + + TArray BaseVersionDependAssetsList; + InBaseVersion.AssetsDependenciesMap.Find(NewVersionAssetModule)->AssetDependencyDetails.GetKeys(BaseVersionDependAssetsList); + + const TMap& NewVersionAssetModuleDetail = InNewVersion.AssetsDependenciesMap.Find(NewVersionAssetModule)->AssetDependencyDetails; + TArray CurrentModuleAssetList; + NewVersionAssetModuleDetail.GetKeys(CurrentModuleAssetList); + + // add to TArray + for (const auto& NewAssetItem : CurrentModuleAssetList) + { + if (!BaseVersionDependAssetsList.Contains(NewAssetItem)) + { + FString BelongModuneName = UFlibAssetManageHelper::GetAssetBelongModuleName(NewAssetItem); + if (!OutAddAsset.AssetsDependenciesMap.Contains(BelongModuneName)) + { + OutAddAsset.AssetsDependenciesMap.Add(BelongModuneName, FAssetDependenciesDetail{ BelongModuneName,TMap{} }); + } + TMap& CurrentModuleAssetDetails = OutAddAsset.AssetsDependenciesMap.Find(BelongModuneName)->AssetDependencyDetails; + CurrentModuleAssetDetails.Add(NewAssetItem, *NewVersionAssetModuleDetail.Find(NewAssetItem)); + } + } + } + } + + // Parser Modify Asset + for (const auto& BaseVersionAssetModule : InBaseAssetModuleKeysList) + { + const FAssetDependenciesDetail& BaseVersionModuleAssetsDetail = *InBaseVersion.AssetsDependenciesMap.Find(BaseVersionAssetModule); + const FAssetDependenciesDetail& NewVersionModuleAssetsDetail = *InNewVersion.AssetsDependenciesMap.Find(BaseVersionAssetModule); + + + if (InNewVersion.AssetsDependenciesMap.Contains(BaseVersionAssetModule)) + { + TArray BeseVersionCurrentModuleAssetListKeys; + BaseVersionModuleAssetsDetail.AssetDependencyDetails.GetKeys(BeseVersionCurrentModuleAssetListKeys); + + for (const auto& AssetItem : BeseVersionCurrentModuleAssetListKeys) + { + const FAssetDetail* BaseVersionAssetDetail = BaseVersionModuleAssetsDetail.AssetDependencyDetails.Find(AssetItem); + const FAssetDetail* NewVersionAssetDetail = NewVersionModuleAssetsDetail.AssetDependencyDetails.Find(AssetItem); + if (!NewVersionAssetDetail) + { + if (!OutDeleteAsset.AssetsDependenciesMap.Contains(BaseVersionAssetModule)) + { + OutDeleteAsset.AssetsDependenciesMap.Add(BaseVersionAssetModule, FAssetDependenciesDetail{ BaseVersionAssetModule,TMap{} }); + } + OutDeleteAsset.AssetsDependenciesMap.Find(BaseVersionAssetModule)->AssetDependencyDetails.Add(AssetItem, *BaseVersionAssetDetail); + continue; + } + + if (!(*NewVersionAssetDetail == *BaseVersionAssetDetail)) + { + UE_LOG(LogHotPatcher,Display,TEXT("Modify Asset: %s"),*AssetItem); + if (!OutModifyAsset.AssetsDependenciesMap.Contains(BaseVersionAssetModule)) + { + OutModifyAsset.AssetsDependenciesMap.Add(BaseVersionAssetModule, FAssetDependenciesDetail{ BaseVersionAssetModule,TMap{} }); + } + OutModifyAsset.AssetsDependenciesMap.Find(BaseVersionAssetModule)->AssetDependencyDetails.Add(AssetItem, *NewVersionAssetDetail); + } + } + } + } + + } + + return true; +} + + +bool UFlibPatchParserHelper::DiffVersionAllPlatformExFiles( + const FExportPatchSettings& PatchSetting, + const FHotPatcherVersion& InBaseVersion,const FHotPatcherVersion& InNewVersion, TMap& OutDiff) +{ + OutDiff.Empty(); + auto ParserDiffPlatformExFileLambda = [](const FPlatformExternFiles& InBase,const FPlatformExternFiles& InNew,ETargetPlatform Platform)->FPatchVersionExternDiff + { + FPatchVersionExternDiff result; + result.Platform = Platform; + + auto ParserAddFiles = [](const TArray& InBase,const TArray& InNew,TArray& Out) + { + for (const auto& NewVersionFile : InNew) + { + if (!InBase.Contains(NewVersionFile)) + { + Out.AddUnique(NewVersionFile); + } + } + }; + + // Parser Add Files + ParserAddFiles(InBase.ExternFiles, InNew.ExternFiles, result.AddExternalFiles); + // Parser delete Files + ParserAddFiles(InNew.ExternFiles, InBase.ExternFiles, result.DeleteExternalFiles); + + auto ParserModifyFiles = [](const FPlatformExternFiles& InBase,const FPlatformExternFiles& InNew,TArray& Out) + { + for (const auto& NewVersionFile : InNew.ExternFiles) + { + // UE_LOG(LogHotPatcher, Log, TEXT("check file %s."), *NewVersionFile); + if (InBase.ExternFiles.Contains(NewVersionFile)) + { + uint32 BaseFileIndex = InBase.ExternFiles.Find(NewVersionFile); + bool bIsSame = (NewVersionFile.FileHash == InBase.ExternFiles[BaseFileIndex].FileHash); + if (!bIsSame) + { + // UE_LOG(LogHotPatcher, Log, TEXT("%s is not same."), *NewVersionFile.MountPath); + Out.Add(NewVersionFile); + }else + { + // UE_LOG(LogHotPatcher, Log, TEXT("%s is same."), *NewVersionFile.MountPath); + } + } + else + { + // UE_LOG(LogHotPatcher, Log, TEXT("base version not contains %s."), *NewVersionFile.MountPath); + } + } + }; + + // Parser modify Files + ParserModifyFiles(InBase,InNew,result.ModifyExternalFiles); + + return result; + }; + + TArray NewPlatformKeys; + TArray BasePlatformKeys; + InNewVersion.PlatformAssets.GetKeys(NewPlatformKeys); + InBaseVersion.PlatformAssets.GetKeys(BasePlatformKeys); + + // ADD + for(const auto& Platform:NewPlatformKeys) + { + if(InBaseVersion.PlatformAssets.Contains(Platform)) + { + OutDiff.Add(Platform,ParserDiffPlatformExFileLambda( + UFlibPatchParserHelper::GetAllExFilesByPlatform(InBaseVersion.PlatformAssets[Platform],false,PatchSetting.GetHashCalculator()), + UFlibPatchParserHelper::GetAllExFilesByPlatform(InNewVersion.PlatformAssets[Platform],true,PatchSetting.GetHashCalculator()), + Platform + )); + } + else + { + FPlatformExternFiles EmptyBase; + EmptyBase.Platform = Platform; + OutDiff.Add(Platform,ParserDiffPlatformExFileLambda( + EmptyBase, + UFlibPatchParserHelper::GetAllExFilesByPlatform(InNewVersion.PlatformAssets[Platform],true,PatchSetting.GetHashCalculator()), + Platform + )); + } + } + + // Delete + for(const auto& Platform:BasePlatformKeys) + { + if(!InNewVersion.PlatformAssets.Contains(Platform)) + { + FPlatformExternFiles EmptyNew; + EmptyNew.Platform = Platform; + OutDiff.Add(Platform,ParserDiffPlatformExFileLambda( + UFlibPatchParserHelper::GetAllExFilesByPlatform(InBaseVersion.PlatformAssets[Platform],false,PatchSetting.GetHashCalculator()), + EmptyNew, + Platform + )); + } + } + return true; +} + +FPlatformExternFiles UFlibPatchParserHelper::GetAllExFilesByPlatform( + const FPlatformExternAssets& InPlatformConf, bool InGeneratedHash, EHashCalculator HashCalculator) +{ + FPlatformExternFiles result; + result.ExternFiles = UFlibPatchParserHelper::ParserExDirectoryAsExFiles(InPlatformConf.AddExternDirectoryToPak); + + for (auto& ExFile : InPlatformConf.AddExternFileToPak) + { + // ignore FPaths::FileExists(ExFile.FilePath.FilePath) + if (!ExFile.FilePath.FilePath.IsEmpty() && !result.ExternFiles.Contains(ExFile)) + { + FExternFileInfo CurrentFile = ExFile; + CurrentFile.FilePath.FilePath = UFlibPatchParserHelper::ReplaceMarkPath(ExFile.FilePath.FilePath); + CurrentFile.MountPath = ExFile.MountPath; + result.ExternFiles.Add(CurrentFile); + } + } + if (InGeneratedHash) + { + for (auto& ExFile : result.ExternFiles) + { + ExFile.GenerateFileHash(HashCalculator); + } + } + return result; +} + +bool UFlibPatchParserHelper::GetPakFileInfo(const FString& InFile, FPakFileInfo& OutFileInfo) +{ + bool bRunStatus = false; + if (FPaths::FileExists(InFile)) + { + FString PathPart; + FString FileNamePart; + FString ExtensionPart; + + FPaths::Split(InFile, PathPart, FileNamePart, ExtensionPart); + + FMD5Hash CurrentPakHash = FMD5Hash::HashFile(*InFile); + OutFileInfo.FileName = FString::Printf(TEXT("%s.%s"),*FileNamePart,*ExtensionPart); + OutFileInfo.Hash = LexToString(CurrentPakHash); + OutFileInfo.FileSize = IFileManager::Get().FileSize(*InFile); + bRunStatus = true; + } + return bRunStatus; +} + +TArray UFlibPatchParserHelper::GetCookedGlobalShaderCacheFiles(const FString& InProjectDir, const FString& InPlatformName) +{ + TArray Resault; + // if (UFlibAssetManageHelper::IsValidPlatform(InPlatformName)) + { + FString CookedEngineFolder = FPaths::Combine(InProjectDir,TEXT("Saved/Cooked"),InPlatformName,TEXT("Engine")); + if (FPaths::DirectoryExists(CookedEngineFolder)) + { + TArray FoundGlobalShaderCacheFiles; + IFileManager::Get().FindFiles(FoundGlobalShaderCacheFiles, *CookedEngineFolder, TEXT("bin")); + + for (const auto& GlobalShaderCache : FoundGlobalShaderCacheFiles) + { + Resault.AddUnique(FPaths::Combine(CookedEngineFolder, GlobalShaderCache)); + } + } + } + return Resault; +} + + +bool UFlibPatchParserHelper::GetCookedAssetRegistryFiles(const FString& InProjectAbsDir,const FString& InProjectName, const FString& InPlatformName, FString& OutFiles) +{ + bool bRunStatus = false; + // if (UFlibAssetManageHelper::IsValidPlatform(InPlatformName)) + { + FString CookedPAssetRegistryFile = FPaths::Combine(InProjectAbsDir, TEXT("Saved/Cooked"), InPlatformName, InProjectName,TEXT("AssetRegistry.bin")); + if (FPaths::FileExists(CookedPAssetRegistryFile)) + { + bRunStatus = true; + OutFiles = CookedPAssetRegistryFile; + } + } + + return bRunStatus; +} + +bool UFlibPatchParserHelper::GetCookedShaderBytecodeFiles(const FString& InProjectAbsDir, const FString& InProjectName, const FString& InPlatformName, bool InGalobalBytecode, bool InProjectBytecode, TArray& OutFiles) +{ + bool bRunStatus = false; + OutFiles.Reset(); + + // if (UFlibAssetManageHelper::IsValidPlatform(InPlatformName)) + { + FString CookedContentDir = FPaths::Combine(InProjectAbsDir, TEXT("Saved/Cooked"), InPlatformName, InProjectName, TEXT("Content")); + + if (FPaths::DirectoryExists(CookedContentDir)) + { + TArray ShaderbytecodeFiles; + IFileManager::Get().FindFiles(ShaderbytecodeFiles, *CookedContentDir, TEXT("ushaderbytecode")); + + for (const auto& ShaderByteCodeFile : ShaderbytecodeFiles) + { + if (InGalobalBytecode && ShaderByteCodeFile.Contains(TEXT("Global"))) + { + OutFiles.AddUnique(FPaths::Combine(CookedContentDir, ShaderByteCodeFile)); + } + if (InProjectBytecode && ShaderByteCodeFile.Contains(InProjectName)) + { + OutFiles.AddUnique(FPaths::Combine(CookedContentDir, ShaderByteCodeFile)); + } + + } + + bRunStatus = !!ShaderbytecodeFiles.Num(); + } + } + return bRunStatus; +} + +TArray UFlibPatchParserHelper::GetProjectIniFiles(const FString& InProjectDir, const FString& InPlatformName) +{ + FString ConfigFolder = FPaths::Combine(InProjectDir, TEXT("Config")); + return UFlibPatchParserHelper::GetIniConfigs(ConfigFolder,InPlatformName); + +} + +bool UFlibPatchParserHelper::ConvIniFilesToPakCommands( + const FString& InEngineAbsDir, + const FString& InProjectAbsDir, + const FString& InProjectName, + // const TArray& InPakOptions, + const TArray& InIniFiles, + TArray& OutCommands, + TFunction InReceiveCommand +) +{ + OutCommands.Reset(); + bool bRunStatus = false; + if (!FPaths::DirectoryExists(InProjectAbsDir) || !FPaths::DirectoryExists(InEngineAbsDir)) + return false; + FString UProjectFile = FPaths::Combine(InProjectAbsDir, InProjectName + TEXT(".uproject")); + if (!FPaths::FileExists(UProjectFile)) + return false; + + for (const auto& IniFile : InIniFiles) + { + bool bIsInProjectIni = false; + if (IniFile.Contains(InProjectAbsDir)) + { + bIsInProjectIni = true; + } + + { + FString IniAbsDir; + FString IniFileName; + FString IniExtention; + FPaths::Split(IniFile, IniAbsDir, IniFileName, IniExtention); + + FString RelativePath; + + if (bIsInProjectIni) + { + RelativePath = FString::Printf( + TEXT("../../../%s/"), + *InProjectName + ); + + FString RelativeToProjectDir = UKismetStringLibrary::GetSubstring(IniAbsDir, InProjectAbsDir.Len(), IniAbsDir.Len() - InProjectAbsDir.Len()); + RelativePath = FPaths::Combine(RelativePath, RelativeToProjectDir); + } + else + { + RelativePath = FString::Printf( + TEXT("../../../Engine/") + ); + FString RelativeToEngineDir = UKismetStringLibrary::GetSubstring(IniAbsDir, InEngineAbsDir.Len(), IniAbsDir.Len() - InEngineAbsDir.Len()); + RelativePath = FPaths::Combine(RelativePath, RelativeToEngineDir); + } + + FString IniFileNameWithExten = FString::Printf(TEXT("%s.%s"),*IniFileName,*IniExtention); + FString CookedIniRelativePath = FPaths::Combine(RelativePath, IniFileNameWithExten); + FString CookCommand = FString::Printf( + TEXT("\"%s\" \"%s\""), + *IniFile, + *CookedIniRelativePath + ); + FPakCommand CurrentPakCommand; + CurrentPakCommand.PakCommands = TArray{ CookCommand }; + CurrentPakCommand.MountPath = CookedIniRelativePath; + CurrentPakCommand.AssetPackage = UFlibPatchParserHelper::MountPathToRelativePath(CurrentPakCommand.MountPath); + InReceiveCommand(CurrentPakCommand); + OutCommands.AddUnique(CookCommand); + + bRunStatus = true; + } + } + return bRunStatus; +} + +bool UFlibPatchParserHelper::ConvNotAssetFileToPakCommand( + const FString& InProjectDir, + const FString& InPlatformName, + // const TArray& InPakOptions, + const FString& InCookedFile, + FString& OutCommand, + TFunction InReceiveCommand +) +{ + bool bRunStatus = false; + if (FPaths::FileExists(InCookedFile)) + { + FString CookPlatformAbsPath = FPaths::Combine(InProjectDir, TEXT("Saved/Cooked"), InPlatformName); + + FString RelativePath = UKismetStringLibrary::GetSubstring(InCookedFile, CookPlatformAbsPath.Len() + 1, InCookedFile.Len() - CookPlatformAbsPath.Len()); + FString AssetFileRelativeCookPath = FString::Printf( + TEXT("../../../%s"), + *RelativePath + ); + + OutCommand = FString::Printf( + TEXT("\"%s\" \"%s\""), + *InCookedFile, + *AssetFileRelativeCookPath + ); + FPakCommand CurrentPakCommand; + CurrentPakCommand.PakCommands = TArray{ OutCommand }; + CurrentPakCommand.MountPath = AssetFileRelativeCookPath; + CurrentPakCommand.AssetPackage = UFlibPatchParserHelper::MountPathToRelativePath(CurrentPakCommand.MountPath); + InReceiveCommand(CurrentPakCommand); + bRunStatus = true; + } + return bRunStatus; +} + +// bool UFlibPatchParserHelper::ConvNotAssetFileToExFile(const FString& InProjectDir, const FString& InPlatformName, const FString& InCookedFile, FExternFileInfo& OutExFile) +// { +// bool bRunStatus = false; +// if (FPaths::FileExists(InCookedFile)) +// { +// FString CookPlatformAbsPath = FPaths::Combine(InProjectDir, TEXT("Saved/Cooked"), InPlatformName); +// +// FString RelativePath = UKismetStringLibrary::GetSubstring(InCookedFile, CookPlatformAbsPath.Len() + 1, InCookedFile.Len() - CookPlatformAbsPath.Len()); +// FString AssetFileRelativeCookPath = FString::Printf( +// TEXT("../../../%s"), +// *RelativePath +// ); +// +// OutExFile.FilePath.FilePath = InCookedFile; +// OutExFile.MountPath = AssetFileRelativeCookPath; +// OutExFile.GetFileHash(); +// bRunStatus = true; +// } +// return bRunStatus; +// } + +FString UFlibPatchParserHelper::HashStringWithSHA1(const FString &InString) +{ + FSHAHash StringHash; + FSHA1::HashBuffer(TCHAR_TO_ANSI(*InString), InString.Len(), StringHash.Hash); + return StringHash.ToString(); + +} + + +TArray UFlibPatchParserHelper::GetIniConfigs(const FString& InSearchDir, const FString& InPlatformName) +{ + TArray Result; + const FString SearchConfigAbsDir = FPaths::ConvertRelativePathToFull(InSearchDir); + + if (FPaths::DirectoryExists(SearchConfigAbsDir)) + { + FFileArrayDirectoryVisitor Visitor; + + IFileManager::Get().IterateDirectory(*SearchConfigAbsDir, Visitor); + + for (const auto& IniFile : Visitor.Files) + { + if (!FPaths::GetCleanFilename(IniFile).Contains(TEXT("Editor"))) + { + Result.Add(IniFile); + } + } + int32 PlatformNameBeginIndex = SearchConfigAbsDir.Len() + 1; + for (const auto& PlatformIniDirectory : Visitor.Directories) + { + FString DirectoryName = UKismetStringLibrary::GetSubstring(PlatformIniDirectory, PlatformNameBeginIndex, PlatformIniDirectory.Len() - PlatformNameBeginIndex); + if (InPlatformName.Contains(DirectoryName)) + { + FFileArrayDirectoryVisitor PlatformVisitor; + + IFileManager::Get().IterateDirectory(*PlatformIniDirectory, PlatformVisitor); + + for (const auto& PlatformIni : PlatformVisitor.Files) + { + Result.Add(PlatformIni); + } + } + } + } + return Result; +} + +TArray UFlibPatchParserHelper::GetEngineConfigs(const FString& InPlatformName) +{ + return UFlibPatchParserHelper::GetIniConfigs(FPaths::EngineConfigDir() , InPlatformName); +} + +TArray UFlibPatchParserHelper::GetEnabledPluginConfigs(const FString& InPlatformName) +{ + TArray result; + TArray> AllEnablePlugins = IPluginManager::Get().GetEnabledPlugins(); + + for (const auto& Plugin : AllEnablePlugins) + { + FString PluginAbsPath; + if (UFlibAssetManageHelper::GetPluginModuleAbsDir(Plugin->GetName(), PluginAbsPath)) + { + FString PluginIniPath = FPaths::Combine(PluginAbsPath, TEXT("Config")); + result.Append(UFlibPatchParserHelper::GetIniConfigs(PluginIniPath, InPlatformName)); + + } + } + + return result; +} + + +TArray UFlibPatchParserHelper::ParserExDirectoryAsExFiles(const TArray& InExternDirectorys) +{ + TArray result; + + if (!InExternDirectorys.Num()) + return result; + + for (const auto& DirectoryItem : InExternDirectorys) + { + if(DirectoryItem.DirectoryPath.Path.IsEmpty()) + continue; + FString DirAbsPath = UFlibPatchParserHelper::ReplaceMarkPath(DirectoryItem.DirectoryPath.Path); //FPaths::ConvertRelativePathToFull(DirectoryItem.DirectoryPath.Path); + bool bWildcard = DirectoryItem.bWildcard; + FString Wildcard = DirectoryItem.WildcardStr; + FPaths::MakeStandardFilename(DirAbsPath); + if (!DirAbsPath.IsEmpty() && FPaths::DirectoryExists(DirAbsPath)) + { + TArray DirectoryAllFiles; + if (UFlibAssetManageHelper::FindFilesRecursive(DirAbsPath, DirectoryAllFiles, true)) + { + int32 ParentDirectoryIndex = DirAbsPath.Len(); + for (const auto& File : DirectoryAllFiles) + { + FString RelativeParentPath = UKismetStringLibrary::GetSubstring(File, ParentDirectoryIndex, File.Len() - ParentDirectoryIndex); + FString RelativeMountPointPath = FPaths::Combine(DirectoryItem.MountPoint, RelativeParentPath); + FPaths::MakeStandardFilename(RelativeMountPointPath); + + FExternFileInfo CurrentFile; + CurrentFile.FilePath.FilePath = File; + + CurrentFile.MountPath = RelativeMountPointPath; + bool bCanAdd = true; + if(bWildcard) + { + bCanAdd = File.MatchesWildcard(Wildcard) && RelativeMountPointPath.MatchesWildcard(Wildcard); + } + if (!result.Contains(CurrentFile) && bCanAdd) + { + result.Add(CurrentFile); + } + } + } + } + } + return result; +} + + +TArray UFlibPatchParserHelper::ParserExFilesInfoAsAssetDetailInfo(const TArray& InExFiles) +{ + TArray result; + + for (auto& File : InExFiles) + { + FAssetDetail CurrentFile; + CurrentFile.AssetType = TEXT("ExternalFile"); + CurrentFile.PackagePath = FName(*File.MountPath); + //CurrentFile.mGuid = File.GetFileHash(); + result.Add(CurrentFile); + } + + return result; + +} + + +TArray UFlibPatchParserHelper::GetIniFilesByPakInternalInfo(const FPakInternalInfo& InPakInternalInfo, const FString& PlatformName) +{ + TArray Inis; + if (InPakInternalInfo.bIncludeEngineIni) + { + Inis.Append(UFlibPatchParserHelper::GetEngineConfigs(PlatformName)); + } + if (InPakInternalInfo.bIncludePluginIni) + { + Inis.Append(UFlibPatchParserHelper::GetEnabledPluginConfigs(PlatformName)); + } + if (InPakInternalInfo.bIncludeProjectIni) + { + Inis.Append(UFlibPatchParserHelper::GetProjectIniFiles(FPaths::ProjectDir(), PlatformName)); + } + + return Inis; +} + + +TArray UFlibPatchParserHelper::GetCookedFilesByPakInternalInfo(const FPakInternalInfo& InPakInternalInfo, const FString& PlatformName) +{ + TArray SearchAssetList; + + FString ProjectAbsDir = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir()); + FString ProjectName = UFlibPatchParserHelper::GetProjectName(); + + if (InPakInternalInfo.bIncludeAssetRegistry) + { + FString AssetRegistryCookCommand; + if (UFlibPatchParserHelper::GetCookedAssetRegistryFiles(ProjectAbsDir, UFlibPatchParserHelper::GetProjectName(), PlatformName, AssetRegistryCookCommand)) + { + SearchAssetList.AddUnique(AssetRegistryCookCommand); + } + } + + if (InPakInternalInfo.bIncludeGlobalShaderCache) + { + TArray GlobalShaderCacheList = UFlibPatchParserHelper::GetCookedGlobalShaderCacheFiles(ProjectAbsDir, PlatformName); + if (!!GlobalShaderCacheList.Num()) + { + SearchAssetList.Append(GlobalShaderCacheList); + } + } + + if (InPakInternalInfo.bIncludeShaderBytecode) + { + TArray ShaderByteCodeFiles; + + if (UFlibPatchParserHelper::GetCookedShaderBytecodeFiles(ProjectAbsDir, ProjectName, PlatformName, true, true, ShaderByteCodeFiles) && + !!ShaderByteCodeFiles.Num() + ) + { + SearchAssetList.Append(ShaderByteCodeFiles); + } + } + return SearchAssetList; +} + + +// TArray UFlibPatchParserHelper::GetInternalFilesAsExFiles(const FPakInternalInfo& InPakInternalInfo, const FString& InPlatformName) +// { +// TArray resultFiles; +// +// TArray AllNeedPakInternalCookedFiles; +// +// FString ProjectAbsDir = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir()); +// AllNeedPakInternalCookedFiles.Append(UFlibPatchParserHelper::GetCookedFilesByPakInternalInfo(InPakInternalInfo, InPlatformName)); +// +// for (const auto& File : AllNeedPakInternalCookedFiles) +// { +// FExternFileInfo CurrentFile; +// UFlibPatchParserHelper::ConvNotAssetFileToExFile(ProjectAbsDir,InPlatformName, File, CurrentFile); +// resultFiles.Add(CurrentFile); +// } +// +// return resultFiles; +// } + +TArray UFlibPatchParserHelper::GetPakCommandsFromInternalInfo( + const FPakInternalInfo& InPakInternalInfo, + const FString& PlatformName, + // const TArray& InPakOptions, + TFunction InReceiveCommand +) +{ + TArray OutPakCommands; + TArray AllNeedPakInternalCookedFiles; + + FString ProjectAbsDir = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir()); + AllNeedPakInternalCookedFiles.Append(UFlibPatchParserHelper::GetCookedFilesByPakInternalInfo(InPakInternalInfo, PlatformName)); + + // combine as cook commands + for (const auto& AssetFile : AllNeedPakInternalCookedFiles) + { + FString CurrentCommand; + if (UFlibPatchParserHelper::ConvNotAssetFileToPakCommand(ProjectAbsDir, PlatformName ,/*InPakOptions,*/ AssetFile, CurrentCommand, InReceiveCommand)) + { + OutPakCommands.AddUnique(CurrentCommand); + } + } + + FString EngineAbsDir = FPaths::ConvertRelativePathToFull(FPaths::EngineDir()); + + auto CombineInPakCommands = [&OutPakCommands, &ProjectAbsDir, &EngineAbsDir, &PlatformName, /*&InPakOptions,*/&InReceiveCommand](const TArray& IniFiles) + { + TArray result; + + TArray IniPakCommmands; + UFlibPatchParserHelper::ConvIniFilesToPakCommands( + EngineAbsDir, + ProjectAbsDir, + UFlibPatchParserHelper::GetProjectName(), + // InPakOptions, + IniFiles, + IniPakCommmands, + InReceiveCommand + ); + if (!!IniPakCommmands.Num()) + { + OutPakCommands.Append(IniPakCommmands); + } + }; + + TArray AllNeedPakIniFiles = UFlibPatchParserHelper::GetIniFilesByPakInternalInfo(InPakInternalInfo, PlatformName); + CombineInPakCommands(AllNeedPakIniFiles); + + return OutPakCommands; +} + + +FChunkInfo UFlibPatchParserHelper::CombineChunkInfo(const FChunkInfo& R, const FChunkInfo& L) +{ + FChunkInfo result = R; + + result.ChunkName = FString::Printf(TEXT("%s_%s"), *R.ChunkName, *L.ChunkName); + result.AssetIncludeFilters.Append(L.AssetIncludeFilters); + result.AssetIgnoreFilters.Append(L.AssetIgnoreFilters); + result.bAnalysisFilterDependencies = L.bAnalysisFilterDependencies || R.bAnalysisFilterDependencies; + result.IncludeSpecifyAssets.Append(L.IncludeSpecifyAssets); + // result.AddExternDirectoryToPak.Append(L.AddExternDirectoryToPak); + // result.AddExternFileToPak.Append(L.AddExternFileToPak); + + for(const auto& LChunkPlatform:L.AddExternAssetsToPlatform) + { + int32 FoundIndex = result.AddExternAssetsToPlatform.Find(LChunkPlatform); + if(FoundIndex!=INDEX_NONE) + { + result.AddExternAssetsToPlatform[FoundIndex].AddExternDirectoryToPak.Append(LChunkPlatform.AddExternDirectoryToPak); + result.AddExternAssetsToPlatform[FoundIndex].AddExternFileToPak.Append(LChunkPlatform.AddExternFileToPak); + } + else + { + result.AddExternAssetsToPlatform.Add(LChunkPlatform); + } + } +#define COMBINE_INTERNAL_FILES(InResult,Left,Right,PropertyName)\ + InResult.InternalFiles.PropertyName = Left.InternalFiles.PropertyName || Right.InternalFiles.PropertyName; + + COMBINE_INTERNAL_FILES(result, L, R, bIncludeAssetRegistry); + COMBINE_INTERNAL_FILES(result, L, R, bIncludeGlobalShaderCache); + COMBINE_INTERNAL_FILES(result, L, R, bIncludeShaderBytecode); + COMBINE_INTERNAL_FILES(result, L, R, bIncludeEngineIni); + COMBINE_INTERNAL_FILES(result, L, R, bIncludePluginIni); + COMBINE_INTERNAL_FILES(result, L, R, bIncludeProjectIni); + + return result; +} + +FChunkInfo UFlibPatchParserHelper::CombineChunkInfos(const TArray& Chunks) +{ + FChunkInfo result; + + for (const auto& Chunk : Chunks) + { + result = UFlibPatchParserHelper::CombineChunkInfo(result, Chunk); + } + return result; +} + +TArray UFlibPatchParserHelper::GetDirectoryPaths(const TArray& InDirectoryPath) +{ + TArray Result; + for (const auto& Filter : InDirectoryPath) + { + if (!Filter.Path.IsEmpty()) + { + FString Path = Filter.Path; + if(Path.StartsWith(TEXT("/All/"))) + { + Path.RemoveFromStart(TEXT("/All")); + } + Result.AddUnique(Path); + } + } + return Result; +} + +TMap UFlibPatchParserHelper::GetAllPlatformExternFilesFromChunk(const FChunkInfo& InChunk, bool bCalcHash) +{ + TMap result; + auto ParserAllFileAndDirs = [](const FPlatformExternAssets& InPlatformInfo,bool bCalcHash=true)->FPlatformExternFiles + { + FPlatformExternFiles result; + result.Platform = InPlatformInfo.TargetPlatform; + result.ExternFiles = InPlatformInfo.AddExternFileToPak; + for (auto& ExFile : InPlatformInfo.AddExternFileToPak) + { + if (!result.ExternFiles.Contains(ExFile)) + { + result.ExternFiles.Add(ExFile); + } + } + if (bCalcHash) + { + for (auto& ExFile : result.ExternFiles) + { + ExFile.GenerateFileHash(); + } + } + return result; + }; + + for(const auto& Platform:InChunk.AddExternAssetsToPlatform) + { + result.Add(Platform.TargetPlatform,ParserAllFileAndDirs(Platform)); + } + + return result; +} + +FChunkAssetDescribe UFlibPatchParserHelper::CollectFChunkAssetsDescribeByChunk( + const FHotPatcherSettingBase* PatcheSettings, + const FPatchVersionDiff& DiffInfo, + 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 + // { + // 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; + + TSet AllSearchFileFilter; + { + for(int32 PlatformIndex=0;PlatformIndex AddFilesRef; + TArray ModifyFilesRef; + if(DiffInfo.PlatformExternDiffInfo.Contains(Platform)) + { + AddFilesRef = DiffInfo.PlatformExternDiffInfo.Find(Platform)->AddExternalFiles; + ModifyFilesRef = DiffInfo.PlatformExternDiffInfo.Find(Platform)->ModifyExternalFiles; + } + + // TArray + auto CollectExtenFilesLambda = [&AllFiles](const TArray& SearchBase, const TSet& Filters,EPatchAssetType Type) + { + for (const auto& Filter : Filters) + { + for (const auto& SearchItem : SearchBase) + { + if (SearchItem.FilePath.FilePath.StartsWith(Filter)) + { + FExternFileInfo FileItem = SearchItem; + FileItem.Type = Type; + AllFiles.Add(FileItem); + } + } + } + }; + CollectExtenFilesLambda(AddFilesRef, AllSearchFileFilter,EPatchAssetType::NEW); + CollectExtenFilesLambda(ModifyFilesRef, AllSearchFileFilter,EPatchAssetType::MODIFY); + return AllFiles; + }; + + for(auto Platform:Platforms) + { + FPlatformExternFiles PlatformFiles; + PlatformFiles.Platform = Platform; + PlatformFiles.ExternFiles = CoolectPlatformExFiles(DiffInfo,Chunk,Platform); + ChunkAssetDescribe.AllPlatformExFiles.Add(Platform,PlatformFiles); + } + + ChunkAssetDescribe.InternalFiles = Chunk.InternalFiles; + return ChunkAssetDescribe; +} + + +TArray UFlibPatchParserHelper::CollectPakCommandsStringsByChunk( + const FPatchVersionDiff& DiffInfo, + const FChunkInfo& Chunk, + const FString& PlatformName, + // const TArray& PakOptions, + const FExportPatchSettings* PatcheSettings +) +{ + TArray ChunkPakCommands; + { + TArray ChunkPakCommands_r = UFlibPatchParserHelper::CollectPakCommandByChunk(DiffInfo, Chunk, PlatformName,/* PakOptions,*/PatcheSettings); + for (const auto& PakCommand : ChunkPakCommands_r) + { + ChunkPakCommands.Append(PakCommand.GetPakCommands()); + } + } + + return ChunkPakCommands; +} + +TArray UFlibPatchParserHelper::CollectPakCommandByChunk( + const FPatchVersionDiff& DiffInfo, + const FChunkInfo& Chunk, + const FString& PlatformName, + // const TArray& PakOptions, + const FExportPatchSettings* PatcheSettings +) +{ + 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); + + bool bIoStore =false; + bool bAllowBulkDataInIoStore = false; +#if ENGINE_MAJOR_VERSION > 4 ||ENGINE_MINOR_VERSION > 25 + if(PatcheSettings) + { + bIoStore = PatcheSettings->GetIoStoreSettings().bIoStore; + bAllowBulkDataInIoStore = PatcheSettings->GetIoStoreSettings().bAllowBulkDataInIoStore; + } +#endif + + auto IsIoStoreAssetsLambda = [bIoStore,bAllowBulkDataInIoStore](const FString& InAbsAssets)->bool + { + bool Matched = false; + if(bIoStore) + { + TArray IoStoreExterns = {TEXT(".uasset"),TEXT(".umap")}; + if(bAllowBulkDataInIoStore) + { + IoStoreExterns.Add(TEXT(".ubulk")); + IoStoreExterns.Add(TEXT(".uptnl")); + } + // bool bIoStoreMatched = false; + for(const auto& IoStoreExrern:IoStoreExterns) + { + if(InAbsAssets.EndsWith(IoStoreExrern)) + { + Matched = true; + break; + } + } + } + return Matched; + }; + + + // Collect Chunk Assets + { + struct PakCommandReceiver + { + PakCommandReceiver(TArray& InPakCommandsRef,EPatchAssetType InType):PakCommands(InPakCommandsRef),Type(InType){} + void operator()(const TArray& InPakCommand,const TArray& InIoStoreCommand, const FString& InMountPath,const FString& InLongPackageName) + { + FPakCommand CurrentPakCommand; + CurrentPakCommand.PakCommands = InPakCommand; + CurrentPakCommand.IoStoreCommands = InIoStoreCommand; + CurrentPakCommand.MountPath = InMountPath; + CurrentPakCommand.AssetPackage = InLongPackageName; + CurrentPakCommand.Type = Type; + PakCommands.Add(CurrentPakCommand); + } + TArray& PakCommands; + EPatchAssetType Type; + }; + // auto ReceivePakCommandAssetLambda = [&PakCommands](const TArray& InPakCommand,const TArray& InIoStoreCommand, const FString& InMountPath,const FString& InLongPackageName) + // { + // + // }; + PakCommandReceiver AddReceiver(PakCommands,EPatchAssetType::NEW); + PakCommandReceiver ModifyReceiver(PakCommands,EPatchAssetType::MODIFY); + FString ProjectDir = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir()); + TArray AssetsPakCommands; + UFlibAssetManageHelper::MakePakCommandFromAssetDependencies( + ProjectDir, + PatcheSettings->GetStorageCookedDir(), + PlatformName, + ChunkAssetsDescrible.AddAssets, + // PakOptions, + AssetsPakCommands, + AddReceiver, + IsIoStoreAssetsLambda + ); + AssetsPakCommands.Empty(); + UFlibAssetManageHelper::MakePakCommandFromAssetDependencies( + ProjectDir, + PatcheSettings->GetStorageCookedDir(), + PlatformName, + ChunkAssetsDescrible.ModifyAssets, + // PakOptions, + AssetsPakCommands, + ModifyReceiver, + IsIoStoreAssetsLambda + ); + } + + // Collect Extern Files + for(const auto& PlatformItenm:CollectPlatforms) + { + for (const auto& CollectFile : ChunkAssetsDescrible.AllPlatformExFiles[PlatformItenm].ExternFiles) + { + FPakCommand CurrentPakCommand; + CurrentPakCommand.MountPath = CollectFile.MountPath; + CurrentPakCommand.AssetPackage = UFlibPatchParserHelper::MountPathToRelativePath(CurrentPakCommand.MountPath); + + // FString PakOptionsStr; + // for (const auto& Param : PakOptions) + // { + // FString FileExtension = FPaths::GetExtension(CollectFile.FilePath.FilePath,false); + // if(IgnoreCompressFormats.Contains(FileExtension) && Param.Equals(TEXT("-compress"),ESearchCase::IgnoreCase)) + // { + // continue; + // } + // PakOptionsStr += TEXT(" ") + Param; + // } + + CurrentPakCommand.PakCommands = TArray{ FString::Printf(TEXT("\"%s\" \"%s\""), *CollectFile.FilePath.FilePath, *CollectFile.MountPath/*,*PakOptionsStr*/) }; + CurrentPakCommand.Type = CollectFile.Type; + PakCommands.Add(CurrentPakCommand); + } + } + // internal files + // TArray NotCompressOptions = PakOptions; + // if(NotCompressOptions.Contains(TEXT("-compress"))) + // NotCompressOptions.Remove(TEXT("-compress")); + auto ReceivePakCommandExFilesLambda = [&PakCommands](const FPakCommand& InCommand){ PakCommands.Emplace(InCommand); }; + UFlibPatchParserHelper::GetPakCommandsFromInternalInfo(ChunkAssetsDescrible.InternalFiles, PlatformName,/* NotCompressOptions,*/ ReceivePakCommandExFilesLambda); + }; + CollectPakCommandsByChunkLambda(DiffInfo, Chunk, PlatformName/*, PakOptions*/); + return PakCommands; +} + +FHotPatcherVersion UFlibPatchParserHelper::ExportReleaseVersionInfoByChunk( + const FString& InVersionId, + const FString& InBaseVersion, + const FString& InDate, + const FChunkInfo& InChunkInfo, + bool InIncludeHasRefAssetsOnly /*= false */, + bool bInAnalysisFilterDependencies /* = true*/ + , EHashCalculator HashCalculator) +{ + TArray AllSkipContents;; + if(InChunkInfo.bForceSkipContent) + { + AllSkipContents.Append(UFlibAssetManageHelper::DirectoriesToStrings(InChunkInfo.ForceSkipContentRules)); + AllSkipContents.Append(UFlibAssetManageHelper::SoftObjectPathsToStrings(InChunkInfo.ForceSkipAssets)); + } + + SCOPED_NAMED_EVENT_TEXT("ExportReleaseVersionInfo",FColor::Red); + FHotPatcherVersion ExportVersion; + { + ExportVersion.VersionId = InVersionId; + ExportVersion.Date = InDate; + ExportVersion.BaseVersionId = InBaseVersion; + } + + FAssetScanConfig ScanConfig; + ScanConfig.bPackageTracker = false; + ScanConfig.AssetIncludeFilters = InChunkInfo.AssetIncludeFilters; + ScanConfig.AssetIgnoreFilters = InChunkInfo.AssetIgnoreFilters; + ScanConfig.bAnalysisFilterDependencies = bInAnalysisFilterDependencies; + ScanConfig.bRecursiveWidgetTree = false; + ScanConfig.IncludeSpecifyAssets = InChunkInfo.IncludeSpecifyAssets; + ScanConfig.bForceSkipContent = InChunkInfo.bForceSkipContent; + ScanConfig.ForceSkipAssets = InChunkInfo.ForceSkipAssets; + ScanConfig.ForceSkipClasses = InChunkInfo.ForceSkipClasses; + ScanConfig.ForceSkipContentRules = InChunkInfo.ForceSkipContentRules; + ScanConfig.AssetRegistryDependencyTypes = InChunkInfo.AssetRegistryDependencyTypes; + ScanConfig.bIncludeHasRefAssetsOnly = InIncludeHasRefAssetsOnly; + RunAssetScanner(ScanConfig,ExportVersion); + + bool CalcHash = HashCalculator == EHashCalculator::NoHash ? false : true; + ExportExternAssetsToPlatform(InChunkInfo.AddExternAssetsToPlatform,ExportVersion,CalcHash,HashCalculator); + + return ExportVersion; +} + +void UFlibPatchParserHelper::RunAssetScanner(FAssetScanConfig ScanConfig,FHotPatcherVersion& ExportVersion) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibPatchParserHelper::RunAssetScanner",FColor::Red); + + TSharedPtr ObjectTracker; + if(ScanConfig.bPackageTracker) + { + ObjectTracker = MakeShareable(new FPackageTrackerBase); + } + // add Include Filter + { + TArray AllIncludeFilter; + for (const auto& Filter : ScanConfig.AssetIncludeFilters) + { + AllIncludeFilter.AddUnique(Filter.Path); + ExportVersion.IncludeFilter.AddUnique(Filter.Path); + } + } + // add Ignore Filter + { + TArray AllIgnoreFilter; + for (const auto& Filter : ScanConfig.AssetIgnoreFilters) + { + AllIgnoreFilter.AddUnique(Filter.Path); + ExportVersion.IgnoreFilter.AddUnique(Filter.Path); + } + } + ExportVersion.bIncludeHasRefAssetsOnly = ScanConfig.bIncludeHasRefAssetsOnly; + ExportVersion.IncludeSpecifyAssets = ScanConfig.IncludeSpecifyAssets; + + TSet IgnoreTypes = + { + TEXT("EditorUtilityBlueprint") + }; + + FAssetDependencies AssetConfig; + AssetConfig.IncludeFilters = UFlibAssetManageHelper::NormalizeContentDirs(ExportVersion.IncludeFilter); + AssetConfig.IgnoreFilters = UFlibAssetManageHelper::NormalizeContentDirs(ExportVersion.IgnoreFilter); + + AssetConfig.AssetRegistryDependencyTypes = ScanConfig.AssetRegistryDependencyTypes; + AssetConfig.InIncludeSpecifyAsset = ScanConfig.IncludeSpecifyAssets; + + + if(ScanConfig.bForceSkipContent) + { + TArray AllSkipContents;; + AllSkipContents.Append(UFlibAssetManageHelper::DirectoriesToStrings(ScanConfig.ForceSkipContentRules)); + for(auto Class:ScanConfig.ForceSkipClasses) + { + IgnoreTypes.Add(*Class->GetName()); + } + AssetConfig.ForceSkipContents = UFlibAssetManageHelper::NormalizeContentDirs(AllSkipContents); + AssetConfig.ForceSkipPackageNames = UFlibAssetManageHelper::SoftObjectPathsToStrings(ScanConfig.ForceSkipAssets); + } + + AssetConfig.bRedirector = true; + AssetConfig.AnalysicFilterDependencies = ScanConfig.bAnalysisFilterDependencies; + AssetConfig.IncludeHasRefAssetsOnly = ScanConfig.bIncludeHasRefAssetsOnly; + AssetConfig.IgnoreAseetTypes = IgnoreTypes; + AssetConfig.bSupportWorldComposition = ScanConfig.bSupportWorldComposition; + { + SCOPED_NAMED_EVENT_TEXT("parser all uasset dependencies(optimized)",FColor::Red); + FAssetDependenciesParser Parser; + Parser.Parse(AssetConfig); + + UE_LOG(LogHotPatcher,Display,TEXT("FAssetDependenciteParser Asset Num (Optimized) %d"),Parser.GetrParseResults().Num()); + + { + SCOPED_NAMED_EVENT_TEXT("combine all assets to FAssetDependenciesInfo",FColor::Red); + FCriticalSection SynchronizationObject; + const TArray& ParseResultSet = Parser.GetrParseResults().Array(); + ParallelFor(Parser.GetrParseResults().Num(),[&](int32 index) + { + FName LongPackageName = ParseResultSet[index]; + if(LongPackageName.IsNone()) + { + return; + } + FAssetDetail CurrentDetail; + if(UFlibAssetManageHelper::GetSpecifyAssetDetail(LongPackageName.ToString(),CurrentDetail)) + { + UFlibAssetManageHelper::IsRedirector(CurrentDetail,CurrentDetail); + { + FScopeLock Lock(&SynchronizationObject); + ExportVersion.AssetInfo.AddAssetsDetail(CurrentDetail); + } + } + },GForceSingleThread); + } + } + + if(ObjectTracker && ScanConfig.bPackageTracker) + { + SCOPED_NAMED_EVENT_TEXT("Combine Package Tracker Assets",FColor::Red); + const TArray LoadedPackages = ObjectTracker->GetLoadedPackages().Array(); + + FCriticalSection SynchronizationObject; + ParallelFor(LoadedPackages.Num(),[&](int32 index) + { + FAssetDetail LoadedPackageDetail = UFlibAssetManageHelper::GetAssetDetailByPackageName(LoadedPackages[index].ToString()); + UFlibAssetManageHelper::IsRedirector(LoadedPackageDetail,LoadedPackageDetail); + + { + FScopeLock Lock(&SynchronizationObject); + ExportVersion.AssetInfo.AddAssetsDetail(LoadedPackageDetail); + } + },GForceSingleThread); + } +} + +void UFlibPatchParserHelper::ExportExternAssetsToPlatform( + const TArray& AddExternAssetsToPlatform, FHotPatcherVersion& ExportVersion, bool + bGenerateHASH, EHashCalculator HashCalculator) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibPatchParserHelper::ExportExternAssetsToPlatform",FColor::Red); + for(const auto& PlatformExInfo:AddExternAssetsToPlatform) + { + FPlatformExternFiles PlatformFileInfo = UFlibPatchParserHelper::GetAllExFilesByPlatform(PlatformExInfo,bGenerateHASH,HashCalculator); + FPlatformExternAssets PlatformExternAssets; + PlatformExternAssets.TargetPlatform = PlatformExInfo.TargetPlatform; + PlatformExternAssets.AddExternFileToPak = PlatformFileInfo.ExternFiles; + ExportVersion.PlatformAssets.Add(PlatformExInfo.TargetPlatform,PlatformExternAssets); + } +} + +TArray UFlibPatchParserHelper::GetPakCommandStrByCommands(const TArray& PakCommands,const TArray& InReplaceTexts,bool bIoStore) +{ + SCOPED_NAMED_EVENT_TEXT("UFlibPatchParserHelper::ExportExternAssetsToPlatform",FColor::Red); + TArray ResultPakCommands; + { + FCriticalSection SynchronizationObject; + ParallelFor(PakCommands.Num(),[&](int32 index) + { + const TArray& PakCommandOriginTexts = bIoStore?PakCommands[index].GetIoStoreCommands():PakCommands[index].GetPakCommands(); + if (!!InReplaceTexts.Num()) + { + for (const auto& PakCommandOriginText : PakCommandOriginTexts) + { + FString PakCommandTargetText = PakCommandOriginText; + for (const auto& ReplaceText : InReplaceTexts) + { + ESearchCase::Type SearchCaseMode = ReplaceText.SearchCase == ESearchCaseMode::CaseSensitive ? ESearchCase::CaseSensitive : ESearchCase::IgnoreCase; + PakCommandTargetText = PakCommandTargetText.Replace(*ReplaceText.From, *ReplaceText.To, SearchCaseMode); + } + { + FScopeLock Lock(&SynchronizationObject); + ResultPakCommands.Add(PakCommandTargetText); + } + } + } + else + { + FScopeLock Lock(&SynchronizationObject); + ResultPakCommands.Append(PakCommandOriginTexts); + } + },GForceSingleThread); + } + return ResultPakCommands; +} + + + +// FHotPatcherAssetDependency UFlibPatchParserHelper::GetAssetRelatedInfo( +// const FAssetDetail& InAsset, +// const TArray& AssetRegistryDependencyTypes, +// TMap& ScanedCaches +// ) +// { +// FHotPatcherAssetDependency Dependency; +// Dependency.Asset = InAsset; +// FString LongPackageName = UFlibAssetManageHelper::PackagePathToLongPackageName(InAsset.PackagePath.ToString()); +// +// { +// TArray SearchAssetDepTypes; +// for (const auto& Type : AssetRegistryDependencyTypes) +// { +// SearchAssetDepTypes.AddUnique(UFlibAssetManageHelper::ConvAssetRegistryDependencyToInternal(Type)); +// } +// +// UFlibAssetManageHelper::GetAssetDependency(LongPackageName, AssetRegistryDependencyTypes,Dependency.AssetDependency, ScanedCaches,false); +// UFlibAssetManageHelper::GetAssetReference(InAsset, SearchAssetDepTypes, Dependency.AssetReference); +// +// } +// +// return Dependency; +// } +// +// TArray UFlibPatchParserHelper::GetAssetsRelatedInfo( +// const TArray& InAssets, +// const TArray& AssetRegistryDependencyTypes, +// TMap& ScanedCaches +// ) +// { +// TArray AssetsDependency; +// +// for (const auto& Asset : InAssets) +// { +// AssetsDependency.Add(UFlibPatchParserHelper::GetAssetRelatedInfo(Asset, AssetRegistryDependencyTypes,ScanedCaches)); +// } +// return AssetsDependency; +// } +// +// TArray UFlibPatchParserHelper::GetAssetsRelatedInfoByFAssetDependencies( +// const FAssetDependenciesInfo& InAssetsDependencies, +// const TArray& AssetRegistryDependencyTypes, +// TMap& ScanedCaches +// ) +// { +// const TArray& AssetsDetail = InAssetsDependencies.GetAssetDetails(); +// return UFlibPatchParserHelper::GetAssetsRelatedInfo(AssetsDetail, AssetRegistryDependencyTypes,ScanedCaches); +// } + + + +bool UFlibPatchParserHelper::GetCookProcCommandParams(const FCookerConfig& InConfig, FString& OutParams) +{ + FString FinalParams; + FinalParams.Append(FString::Printf(TEXT("\"%s\" "), *InConfig.ProjectPath)); + FinalParams.Append(FString::Printf(TEXT("%s "), *InConfig.EngineParams)); + + auto CombineParamsLambda = [&FinalParams,&InConfig](const FString& InName, const TArray& InArray) + { + if(!InArray.Num()) + return; + FinalParams.Append(InName); + + for (int32 Index = 0; Index < InArray.Num(); ++Index) + { + FinalParams.Append(InArray[Index]); + if (!(Index == InArray.Num() - 1)) + { + FinalParams.Append(TEXT("+")); + } + } + FinalParams.Append(TEXT(" ")); + }; + CombineParamsLambda(TEXT("-targetplatform="), InConfig.CookPlatforms); + CombineParamsLambda(TEXT("-Map="), InConfig.CookMaps); + + for (const auto& CookFilter : InConfig.CookFilter) + { + FString FilterFullPath; + if (UFlibAssetManageHelper::ConvRelativeDirToAbsDir(CookFilter, FilterFullPath)) + { + if (FPaths::DirectoryExists(FilterFullPath)) + { + FinalParams.Append(FString::Printf(TEXT("-COOKDIR=\"%s\" "), *FilterFullPath)); + } + } + } + + for (const auto& Option : InConfig.CookSettings) + { + FinalParams.Append(TEXT("-") + Option + TEXT(" ")); + } + + FinalParams.Append(InConfig.Options); + + OutParams = FinalParams; + + return true; +} + +void UFlibPatchParserHelper::ExcludeContentForVersionDiff(FPatchVersionDiff& VersionDiff,const TArray& ExcludeRules,EHotPatcherMatchModEx matchMod) +{ + UFlibAssetManageHelper::ExcludeContentForAssetDependenciesDetail(VersionDiff.AssetDiffInfo.AddAssetDependInfo,ExcludeRules,matchMod); + UFlibAssetManageHelper::ExcludeContentForAssetDependenciesDetail(VersionDiff.AssetDiffInfo.ModifyAssetDependInfo,ExcludeRules,matchMod); + UFlibAssetManageHelper::ExcludeContentForAssetDependenciesDetail(VersionDiff.AssetDiffInfo.DeleteAssetDependInfo,ExcludeRules,matchMod); +} + +FString UFlibPatchParserHelper::MountPathToRelativePath(const FString& InMountPath) +{ + FString Path; + FString filename; + FString RelativePath; +#define RELATIVE_STR_LENGTH 9 + if (InMountPath.StartsWith("../../../")) + { + RelativePath = UKismetStringLibrary::GetSubstring(InMountPath, RELATIVE_STR_LENGTH, InMountPath.Len() - RELATIVE_STR_LENGTH); + } + FString extersion; + FPaths::Split(RelativePath, Path, filename, extersion); + return Path/filename; +} + +#define ENGINEDIR_MARK TEXT("[ENGINEDIR]/") +#define ENGINE_CONTENT_DIR_MARK TEXT("[ENGINE_CONTENT_DIR]/") +#define PROJECTDIR_MARK TEXT("[PROJECTDIR]/") +#define PROJECT_CONTENT_DIR_MARK TEXT("[PROJECT_CONTENT_DIR]/") +#define PROJECT_SAVED_DIR_MARK TEXT("[PROJECT_SAVED_DIR]/") +#define PROJECT_CONFIG_DIR_MARK TEXT("[PROJECT_CONFIG_DIR]/") + +TMap UFlibPatchParserHelper::GetReplacePathMarkMap() +{ + static TMap MarkMap; + static bool bInited = false; + if(!bInited) + { + MarkMap.Add(ENGINEDIR_MARK,FPaths::EngineDir()); + MarkMap.Add(ENGINE_CONTENT_DIR_MARK,FPaths::EngineContentDir()); + MarkMap.Add(PROJECTDIR_MARK,FPaths::ProjectDir()); + MarkMap.Add(PROJECT_CONTENT_DIR_MARK,FPaths::ProjectContentDir()); + MarkMap.Add(PROJECT_SAVED_DIR_MARK,FPaths::ProjectSavedDir()); + MarkMap.Add(PROJECT_CONFIG_DIR_MARK,FPaths::ProjectConfigDir()); + bInited = true; + } + + return MarkMap; +} + +FString UFlibPatchParserHelper::ReplaceMark(const FString& Src) +{ + TMap MarkMap = UFlibPatchParserHelper::GetReplacePathMarkMap(); + TArray MarkKeys; + MarkMap.GetKeys(MarkKeys); + + FString result = Src; + for(const auto& Key:MarkKeys) + { + while(result.Contains(Key)) + { + result = result.Replace(*Key,*FPaths::ConvertRelativePathToFull(*MarkMap.Find(Key))); + } + } + FPaths::NormalizeFilename(result); + FPaths::CollapseRelativeDirectories(result); + return result; +} + +FString UFlibPatchParserHelper::ReplaceMarkPath(const FString& Src) +{ + FString result = UFlibPatchParserHelper::ReplaceMark(Src); + FPaths::MakeStandardFilename(result); + result = FPaths::ConvertRelativePathToFull(result); + return result; +} + +void UFlibPatchParserHelper::ReplacePatherSettingProjectDir(TArray& PlatformAssets) +{ + for(auto& ExPlatform:PlatformAssets) + { + for(auto& ExFile:ExPlatform.AddExternFileToPak) + { + ExFile.FilePath.FilePath = UFlibPatchParserHelper::ReplaceMarkPath(ExFile.FilePath.FilePath); + } + for(auto& ExDir:ExPlatform.AddExternDirectoryToPak) + { + ExDir.DirectoryPath.Path = UFlibPatchParserHelper::ReplaceMarkPath(ExDir.DirectoryPath.Path); + } + } +} + +TArray UFlibPatchParserHelper::GetUnCookUassetExtensions() +{ + TArray UAssetFormats = { TEXT(".uasset"),TEXT(".umap")}; + return UAssetFormats; +} + +TArray UFlibPatchParserHelper::GetCookedUassetExtensions() +{ + TArray UAssetFormats = { TEXT(".uasset"),TEXT(".ubulk"),TEXT(".uexp"),TEXT(".umap"),TEXT(".ufont") }; + return UAssetFormats; +} + +bool UFlibPatchParserHelper::IsCookedUassetExtensions(const FString& InAsset) +{ + return UFlibPatchParserHelper::MatchStrInArray(InAsset,UFlibPatchParserHelper::GetCookedUassetExtensions()); +} +bool UFlibPatchParserHelper::IsUnCookUassetExtension(const FString& InAsset) +{ + return UFlibPatchParserHelper::MatchStrInArray(InAsset,UFlibPatchParserHelper::GetUnCookUassetExtensions()); +} + +bool UFlibPatchParserHelper::MatchStrInArray(const FString& InStr, const TArray& InArray) +{ + bool bResult = false; + for (const auto& Format:InArray) + { + if (InStr.Contains(Format)) + { + bResult = true; + break; + } + } + return bResult; +} + +FString UFlibPatchParserHelper::LoadAESKeyStringFromCryptoFile(const FString& InCryptoJson) +{ + FString result; + if(FPaths::FileExists(InCryptoJson)) + { + FString Content; + FFileHelper::LoadFileToString(Content,*InCryptoJson); + if (!Content.IsEmpty()) + { + TSharedRef> JsonReader = TJsonReaderFactory::Create(Content); + TSharedPtr DeserializeJsonObject; + if (FJsonSerializer::Deserialize(JsonReader, DeserializeJsonObject)) + { + const TSharedPtr* EncryptionKeyObject; + DeserializeJsonObject->TryGetObjectField(TEXT("EncryptionKey"),EncryptionKeyObject); + if(EncryptionKeyObject->IsValid()) + { + FString Key; + EncryptionKeyObject->Get()->TryGetStringField(TEXT("Key"),Key); + if(!Key.IsEmpty()) + { + result = Key; + } + } + } + } + } + return result; +} + +FAES::FAESKey UFlibPatchParserHelper::LoadAESKeyFromCryptoFile(const FString& InCryptoJson) +{ + FAES::FAESKey AESKey; + FString AESKeyString = UFlibPatchParserHelper::LoadAESKeyStringFromCryptoFile(InCryptoJson); + if(!AESKeyString.IsEmpty()) + { + TArray DecodedBuffer; + if (!FBase64::Decode(AESKeyString, DecodedBuffer)) + { + FMemory::Memcpy(AESKey.Key, DecodedBuffer.GetData(), 32); + } + } + return AESKey; +} + +FString UFlibPatchParserHelper::AssetMountPathToAbs(const FString& InAssetMountPath) +{ + FString result; + // ../../../Engine/xxx to ENGINE/ + FString Dest = InAssetMountPath; + while(Dest.StartsWith(TEXT("../"))) + { + Dest.RemoveFromStart(TEXT("../")); + } + + if(Dest.StartsWith(TEXT("Engine"))) + { + Dest.RemoveFromStart(TEXT("Engine/")); + result = FPaths::Combine(FPaths::EngineDir(),Dest); + } + if(Dest.StartsWith(FApp::GetProjectName())) + { + Dest.RemoveFromStart(FString::Printf(TEXT("%s/"),FApp::GetProjectName())); + result = FPaths::Combine(FPaths::ProjectDir(),Dest); + } + FPaths::NormalizeFilename(result); + return FPaths::ConvertRelativePathToFull(result); +} + +FString UFlibPatchParserHelper::UAssetMountPathToPackagePath(const FString& InAssetMountPath) +{ + TMap ReceiveModuleMap; + UFlibAssetManageHelper::GetAllEnabledModuleName(ReceiveModuleMap); + + FString LongPackageName = InAssetMountPath; + + LongPackageName.RemoveFromStart(TEXT("../../..")); + + for(const auto& UnCookAssetExtension:UFlibPatchParserHelper::GetUnCookUassetExtensions()) + { + LongPackageName.RemoveFromEnd(UnCookAssetExtension); + } + + // LongPackageName = LongPackageName.Replace(TEXT("/Content"), TEXT("")); + + FString ModuleName = LongPackageName; + { + ModuleName.RemoveAt(0); + int32 Index; + if (ModuleName.FindChar('/', Index)) + { + ModuleName = ModuleName.Left(Index); + } + } + + if (ModuleName.Equals(FApp::GetProjectName())) + { + LongPackageName.RemoveFromStart(TEXT("/") + ModuleName); + LongPackageName = TEXT("/Game") + LongPackageName; + } + + if (LongPackageName.Contains(TEXT("/Plugins/"))) + { + TArray ModuleKeys; + ReceiveModuleMap.GetKeys(ModuleKeys); + + TArray IgnoreModules = { TEXT("Game"),TEXT("Engine") }; + + for (const auto& IgnoreModule : IgnoreModules) + { + ModuleKeys.Remove(IgnoreModule); + } + + for (const auto& Module : ModuleKeys) + { + FString FindStr = TEXT("/") + Module + TEXT("/"); + if (LongPackageName.Contains(FindStr,ESearchCase::IgnoreCase)) + { + int32 PluginModuleIndex = LongPackageName.Find(FindStr,ESearchCase::IgnoreCase,ESearchDir::FromEnd); + + LongPackageName = LongPackageName.Right(LongPackageName.Len()- PluginModuleIndex-FindStr.Len()); + LongPackageName = TEXT("/") + Module + TEXT("/") + LongPackageName; + break; + } + } + } + + int32 ContentPoint = LongPackageName.Find(TEXT("/Content")); + if(ContentPoint != INDEX_NONE) + { + LongPackageName = FPaths::Combine(LongPackageName.Left(ContentPoint), LongPackageName.Right(LongPackageName.Len() - ContentPoint - 8)); + } + + FString LongPackagePath = UFlibAssetManageHelper::LongPackageNameToPackagePath(LongPackageName); + return LongPackagePath; +} + +bool UFlibPatchParserHelper::GetPluginPakPathByName(const FString& PluginName,FString& uPluginAbsPath,FString& uPluginMountPath) +{ + bool bStatus = false; + TSharedPtr FoundPlugin = IPluginManager::Get().FindPlugin(PluginName); + FString uPluginFileAbsPath; + FString uPluginFileMountPath; + if(FoundPlugin.IsValid()) + { + uPluginFileAbsPath = FPaths::ConvertRelativePathToFull(FPaths::Combine(FoundPlugin->GetBaseDir(),FString::Printf(TEXT("%s.uplugin"),*FoundPlugin->GetName()))); + FPaths::NormalizeFilename(uPluginFileAbsPath); + + uPluginFileMountPath = uPluginFileAbsPath; + + if(FPaths::FileExists(uPluginFileAbsPath)) + { + FString EnginePluginPath = FPaths::ConvertRelativePathToFull(FPaths::EnginePluginsDir()); + FString ProjectPluginPath = FPaths::ConvertRelativePathToFull(FPaths::ProjectPluginsDir()); + if(uPluginFileMountPath.StartsWith(EnginePluginPath)) + { + uPluginFileMountPath.RemoveFromStart(EnginePluginPath); + uPluginFileMountPath = FString::Printf(TEXT("../../../Engine/Plugins/%s"),*uPluginFileMountPath); + } + if(uPluginFileMountPath.StartsWith(ProjectPluginPath)) + { + uPluginFileMountPath.RemoveFromStart(ProjectPluginPath); + uPluginFileMountPath = FString::Printf(TEXT("../../../%s/Plugins/%s"),FApp::GetProjectName(),*uPluginFileMountPath); + } + uPluginAbsPath = uPluginFileAbsPath; + uPluginMountPath = uPluginFileMountPath; + bStatus = true; + } + } + return bStatus; +} + +FString UFlibPatchParserHelper::GetPluginMountPoint(const FString& PluginName) +{ + FString uPluginAbsPath; + FString uPluginMountPath; + if(UFlibPatchParserHelper::GetPluginPakPathByName(PluginName,uPluginAbsPath,uPluginMountPath)) + { + uPluginMountPath.RemoveFromEnd(FString::Printf(TEXT("/%s.uplugin"),*PluginName)); + } + return uPluginMountPath; +} + +FString UFlibPatchParserHelper::ParserMountPointRegular(const FString& Src) +{ + FString result; + if(Src.MatchesWildcard(FString::Printf(TEXT("*%s*"),AS_PROJECTDIR_MARK))) + { + result = Src.Replace(AS_PROJECTDIR_MARK,*FString::Printf(TEXT("../../../%s"),FApp::GetProjectName())); + } + if(Src.MatchesWildcard(FString::Printf(TEXT("*%s*"),AS_PLUGINDIR_MARK))) + { + int32 index = Src.Find(AS_PLUGINDIR_MARK); + index += strlen(TCHAR_TO_ANSI(AS_PLUGINDIR_MARK)); + int32 BeginIndex = index + 1; + int32 EndIndex; + Src.FindLastChar(']',EndIndex); + FString PluginName = UKismetStringLibrary::GetSubstring(Src,BeginIndex,EndIndex-BeginIndex); + result = Src.Replace(AS_PLUGINDIR_MARK,TEXT("")); + result = result.Replace(*FString::Printf(TEXT("[%s]"),*PluginName),*UFlibPatchParserHelper::GetPluginMountPoint(PluginName)); + } + return result; +} + +void UFlibPatchParserHelper::ReloadShaderbytecode() +{ + UFlibPakHelper::ReloadShaderbytecode(); +} + +bool UFlibPatchParserHelper::LoadShaderbytecode(const FString& LibraryName, const FString& LibraryDir) +{ + return UFlibPakHelper::LoadShaderbytecode(LibraryName,LibraryDir); +} + +void UFlibPatchParserHelper::CloseShaderbytecode(const FString& LibraryName) +{ + UFlibPakHelper::CloseShaderbytecode(LibraryName); +} + +#define ENCRYPT_KEY_NAME TEXT("EncryptionKey") +#define ENCRYPT_PAK_INI_FILES_NAME TEXT("bEncryptPakIniFiles") +#define ENCRYPT_PAK_INDEX_NAME TEXT("bEncryptPakIndex") +#define ENCRYPT_UASSET_FILES_NAME TEXT("bEncryptUAssetFiles") +#define ENCRYPT_ALL_ASSET_FILES_NAME TEXT("bEncryptAllAssetFiles") + +#define SIGNING_PAK_SIGNING_NAME TEXT("bEnablePakSigning") +#define SIGNING_MODULES_NAME TEXT("SigningModulus") +#define SIGNING_PUBLIC_EXPONENT_NAME TEXT("SigningPublicExponent") +#define SIGNING_PRIVATE_EXPONENT_NAME TEXT("SigningPrivateExponent") + +FPakEncryptionKeys UFlibPatchParserHelper::GetCryptoByProjectSettings() +{ + FPakEncryptionKeys result; + + result.EncryptionKey.Name = TEXT("Embedded"); + result.EncryptionKey.Guid = FGuid::NewGuid().ToString(); + + UClass* Class = FindObject(ANY_PACKAGE, TEXT("/Script/CryptoKeys.CryptoKeysSettings"), true); + if(Class) + { + FString AESKey; + for(TFieldIterator PropertyIter(Class);PropertyIter;++PropertyIter) + { + FProperty* PropertyIns = *PropertyIter; + // UE_LOG(LogHotPatcher,Log,TEXT("%s"),*PropertyIns->GetName()); + if(PropertyIns->GetName().Equals(ENCRYPT_KEY_NAME)) + { + result.EncryptionKey.Key = *PropertyIns->ContainerPtrToValuePtr(Class->GetDefaultObject()); + } + if(PropertyIns->GetName().Equals(ENCRYPT_PAK_INI_FILES_NAME)) + { + result.bEnablePakIniEncryption = *PropertyIns->ContainerPtrToValuePtr(Class->GetDefaultObject()); + } + if(PropertyIns->GetName().Equals(ENCRYPT_PAK_INDEX_NAME)) + { + result.bEnablePakIndexEncryption = *PropertyIns->ContainerPtrToValuePtr(Class->GetDefaultObject()); + } + if(PropertyIns->GetName().Equals(ENCRYPT_UASSET_FILES_NAME)) + { + result.bEnablePakUAssetEncryption = *PropertyIns->ContainerPtrToValuePtr(Class->GetDefaultObject()); + } + if(PropertyIns->GetName().Equals(ENCRYPT_ALL_ASSET_FILES_NAME)) + { + result.bEnablePakFullAssetEncryption = *PropertyIns->ContainerPtrToValuePtr(Class->GetDefaultObject()); + } + // SIGN + if(PropertyIns->GetName().Equals(SIGNING_PAK_SIGNING_NAME)) + { + result.bEnablePakSigning = *PropertyIns->ContainerPtrToValuePtr(Class->GetDefaultObject()); + } + if(PropertyIns->GetName().Equals(SIGNING_PUBLIC_EXPONENT_NAME)) + { + result.SigningKey.PublicKey.Exponent = *PropertyIns->ContainerPtrToValuePtr(Class->GetDefaultObject()); + } + if(PropertyIns->GetName().Equals(SIGNING_MODULES_NAME)) + { + result.SigningKey.PublicKey.Modulus = *PropertyIns->ContainerPtrToValuePtr(Class->GetDefaultObject()); + result.SigningKey.PrivateKey.Modulus = result.SigningKey.PublicKey.Modulus; + } + if(PropertyIns->GetName().Equals(SIGNING_PRIVATE_EXPONENT_NAME)) + { + result.SigningKey.PrivateKey.Exponent = *PropertyIns->ContainerPtrToValuePtr(Class->GetDefaultObject()); + } + } + } + return result; +} + +FEncryptSetting UFlibPatchParserHelper::GetCryptoSettingsByJson(const FString& CryptoJson) +{ + FEncryptSetting result; + FArchive* File = IFileManager::Get().CreateFileReader(*CryptoJson); + TSharedPtr RootObject; + TSharedRef> Reader = TJsonReaderFactory::Create(File); + if (FJsonSerializer::Deserialize(Reader, RootObject)) + { + result.bSign = RootObject->GetBoolField(TEXT("bEnablePakSigning")); + result.bEncryptIndex = RootObject->GetBoolField(TEXT("bEnablePakIndexEncryption")); + result.bEncryptIniFiles = RootObject->GetBoolField(TEXT("bEnablePakIniEncryption")); + result.bEncryptUAssetFiles = RootObject->GetBoolField(TEXT("bEnablePakUAssetEncryption")); + result.bEncryptAllAssetFiles = RootObject->GetBoolField(TEXT("bEnablePakFullAssetEncryption")); + } + return result; +} + +FEncryptSetting UFlibPatchParserHelper::GetCryptoSettingByPakEncryptSettings(const FPakEncryptSettings& Config) +{ + FEncryptSetting EncryptSettings; + if(Config.bUseDefaultCryptoIni) + { + FPakEncryptionKeys ProjectCrypt = UFlibPatchParserHelper::GetCryptoByProjectSettings(); + EncryptSettings.bEncryptIniFiles = ProjectCrypt.bEnablePakIniEncryption; + EncryptSettings.bEncryptUAssetFiles = ProjectCrypt.bEnablePakUAssetEncryption; + EncryptSettings.bEncryptAllAssetFiles = ProjectCrypt.bEnablePakFullAssetEncryption; + EncryptSettings.bEncryptIndex = ProjectCrypt.bEnablePakIndexEncryption; + EncryptSettings.bSign = ProjectCrypt.bEnablePakSigning; + } + else + { + FString CryptoKeyFile = UFlibPatchParserHelper::ReplaceMark(Config.CryptoKeys.FilePath); + if(FPaths::FileExists(CryptoKeyFile)) + { + FEncryptSetting CryptoJsonSettings = UFlibPatchParserHelper::GetCryptoSettingsByJson(CryptoKeyFile); + EncryptSettings.bEncryptIniFiles = CryptoJsonSettings.bEncryptIniFiles; + EncryptSettings.bEncryptUAssetFiles = CryptoJsonSettings.bEncryptUAssetFiles; + EncryptSettings.bEncryptAllAssetFiles = CryptoJsonSettings.bEncryptAllAssetFiles; + EncryptSettings.bSign = CryptoJsonSettings.bSign; + EncryptSettings.bEncryptIndex = CryptoJsonSettings.bEncryptIndex; + } + } + return EncryptSettings; +} + +bool UFlibPatchParserHelper::SerializePakEncryptionKeyToFile(const FPakEncryptionKeys& PakEncryptionKeys, + const FString& ToFile) +{ + FString KeyInfo; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(PakEncryptionKeys,KeyInfo); + return UFlibAssetManageHelper::SaveStringToFile(ToFile, KeyInfo); +} + +TArray UFlibPatchParserHelper::GetDefaultForceSkipContentDir() +{ + TArray result; + TArray DefaultSkipEditorContentRules = { + TEXT("/Engine/Editor*/") + ,TEXT("/Engine/VREditor/") + }; + + TArray AlwaySkipTempContentRules = { +#if ENGINE_MAJOR_VERSION + TEXT("/Game/__ExternalActors__") + ,TEXT("/Game/__ExternalObjects__") +#endif + }; + + auto AddToSkipDirs = [&result](const TArray& SkipDirs) + { + for(const auto& Ruls:SkipDirs) + { + FDirectoryPath PathIns; + PathIns.Path = Ruls; + result.Add(PathIns); + } + }; + AddToSkipDirs(AlwaySkipTempContentRules); + + bool bSkipEditorContent = false; + GConfig->GetBool(TEXT("/Script/UnrealEd.ProjectPackagingSettings"),TEXT("bSkipEditorContent"),bSkipEditorContent,GGameIni); + + if(bSkipEditorContent) + { + AddToSkipDirs(DefaultSkipEditorContentRules); + } + + return result; +} + +FSHAHash UFlibPatchParserHelper::FileSHA1Hash(const FString& Filename) +{ + FSHAHash Hash; + FSHA1::GetFileSHAHash(*Filename,Hash.Hash,true); + TArray Data; + FFileHelper::LoadFileToArray(Data,*Filename); + FSHA1::HashBuffer(Data.GetData(),Data.Num(),Hash.Hash); + return Hash; +} + +FString UFlibPatchParserHelper::FileHash(const FString& Filename, EHashCalculator Calculator) +{ + FString HashValue; + + FString FileAbsPath = FPaths::ConvertRelativePathToFull(Filename); + if (FPaths::FileExists(FileAbsPath)) + { + switch (Calculator) + { + case EHashCalculator::MD5: + { + FMD5Hash FileMD5Hash = FMD5Hash::HashFile(*FileAbsPath); + HashValue = LexToString(FileMD5Hash); + break; + } + case EHashCalculator::SHA1: + { + FSHAHash Hash = UFlibPatchParserHelper::FileSHA1Hash(Filename); + HashValue = Hash.ToString(); + break; + } + }; + } + return HashValue; +} + +bool UFlibPatchParserHelper::IsValidPatchSettings(const FExportPatchSettings* PatchSettings,bool bExternalFilesCheck) +{ + bool bCanExport = false; + FExportPatchSettings* NoConstSettingObject = const_cast(PatchSettings); + if (NoConstSettingObject) + { + bool bHasBase = false; + if (NoConstSettingObject->IsByBaseVersion()) + bHasBase = !NoConstSettingObject->GetBaseVersion().IsEmpty() && FPaths::FileExists(NoConstSettingObject->GetBaseVersion()); + else + bHasBase = true; + bool bHasVersionId = !NoConstSettingObject->GetVersionId().IsEmpty(); + bool bHasFilter = !!NoConstSettingObject->GetAssetIncludeFilters().Num(); + bool bHasSpecifyAssets = !!NoConstSettingObject->GetIncludeSpecifyAssets().Num(); + bool bHasSavePath = !NoConstSettingObject->GetSaveAbsPath().IsEmpty(); + bool bHasPakPlatfotm = !!NoConstSettingObject->GetPakTargetPlatforms().Num(); + + bool bHasExternFiles = true; + if(bExternalFilesCheck) + { + bHasExternFiles = !!NoConstSettingObject->GetAllPlatfotmExternFiles().Num(); + } + + bool bHasExDirs = !!NoConstSettingObject->GetAddExternAssetsToPlatform().Num(); + + bool bHasAnyPakFiles = ( + bHasFilter || bHasSpecifyAssets || bHasExternFiles || bHasExDirs || + NoConstSettingObject->IsIncludeEngineIni() || + NoConstSettingObject->IsIncludePluginIni() || + NoConstSettingObject->IsIncludeProjectIni() || + NoConstSettingObject->bImportProjectSettings + ); + bCanExport = bHasBase && bHasVersionId && bHasAnyPakFiles && bHasPakPlatfotm && bHasSavePath; + } + return bCanExport; +} + +FString UFlibPatchParserHelper::GetTargetPlatformsStr(const TArray& Platforms) +{ + FString PlatformArrayStr; + TArray UniquePlatforms; + for(ETargetPlatform Platform:Platforms){ UniquePlatforms.AddUnique(Platform);} + for(ETargetPlatform Platform:UniquePlatforms) + { + FString PlatformName = THotPatcherTemplateHelper::GetEnumNameByValue(Platform); + PlatformArrayStr += FString::Printf(TEXT("%s+"),*PlatformName); + } + PlatformArrayStr.RemoveFromEnd(TEXT("+")); + return PlatformArrayStr; +} + +FString UFlibPatchParserHelper::GetTargetPlatformsCmdLine(const TArray& Platforms) +{ + FString Result; + if(Platforms.Num()) + { + FString PlatformArrayStr = UFlibPatchParserHelper::GetTargetPlatformsStr(Platforms); + 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; +}; + +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; +} + + +bool UFlibPatchParserHelper::GetCmdletBoolValue(const FString& Token, bool& OutValue) +{ + FString bTokenValue; + bool bHasToken = FParse::Value(FCommandLine::Get(), *Token.ToLower(), bTokenValue); + if(bHasToken) + { + if(bTokenValue.Equals(TEXT("true"),ESearchCase::IgnoreCase)) + { + OutValue = true; + } + if(bTokenValue.Equals(TEXT("false"),ESearchCase::IgnoreCase)) + { + OutValue = false; + } + } + return bHasToken; +} + +FString UFlibPatchParserHelper::ReplacePakRegular(const FReplacePakRegular& RegularConf, const FString& InRegular) +{ + struct FResularOperator + { + FResularOperator(const FString& InName,TFunction InOperator) + :Name(InName),Do(InOperator){} + FString Name; + TFunction Do; + }; + + TArray RegularOpList; + 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(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 + { + FString Result = Regular; + for(auto& Operator:Operators) + { + Result = Result.Replace(*Operator.Name,*(Operator.Do())); + } + auto ReplaceDoubleLambda = [](FString& Src,const FString& From,const FString& To) + { + while(Src.Contains(From)) + { + Src = Src.Replace(*From,*To); + } + }; + ReplaceDoubleLambda(Result,TEXT("__"),TEXT("_")); + ReplaceDoubleLambda(Result,TEXT("--"),TEXT("-")); + return Result; + }; + + return CustomPakNameRegular(RegularOpList,InRegular); +} diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/FlibShaderPipelineCacheHelper.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/FlibShaderPipelineCacheHelper.cpp new file mode 100644 index 0000000..2b40165 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Private/FlibShaderPipelineCacheHelper.cpp @@ -0,0 +1,95 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "FlibShaderPipelineCacheHelper.h" +#include "FlibPatchParserHelper.h" +#include "HotPatcherLog.h" +#include "ShaderPipelineCache.h" +#include "RHIShaderFormatDefinitions.inl" +#include "HAL/IConsoleManager.h" +#include "Misc/EngineVersionComparison.h" + +bool UFlibShaderPipelineCacheHelper::LoadShaderPipelineCache(const FString& Name) +{ + UE_LOG(LogHotPatcher,Display,TEXT("Load Shader pipeline cache %s for platform %d"),*Name,*ShaderPlatformToShaderFormatName(GMaxRHIShaderPlatform).ToString()); +#if UE_VERSION_OLDER_THAN(5,1,0) + return FShaderPipelineCache::OpenPipelineFileCache(Name,GMaxRHIShaderPlatform); +#else + return FShaderPipelineCache::OpenPipelineFileCache(GMaxRHIShaderPlatform); +#endif +} + +bool UFlibShaderPipelineCacheHelper::EnableShaderPipelineCache(bool bEnable) +{ + UE_LOG(LogHotPatcher,Display,TEXT("EnableShaderPipelineCache %s"),bEnable?TEXT("true"):TEXT("false")); + auto Var = IConsoleManager::Get().FindConsoleVariable(TEXT("r.ShaderPipelineCache.Enabled")); + if(Var) + { + Var->Set( bEnable ? 1 : 0); + } + return !!Var; +} + +bool UFlibShaderPipelineCacheHelper::SavePipelineFileCache(EPSOSaveMode Mode) +{ +#if UE_VERSION_OLDER_THAN(5,1,0) + return FShaderPipelineCache::SavePipelineFileCache((FPipelineFileCache::SaveMode)Mode); +#else + return FShaderPipelineCache::SavePipelineFileCache((FPipelineFileCacheManager::SaveMode)Mode); +#endif +} + +bool UFlibShaderPipelineCacheHelper::EnableLogPSO(bool bEnable) +{ + UE_LOG(LogHotPatcher,Display,TEXT("EnableLogPSO %s"),bEnable?TEXT("true"):TEXT("false")); + auto Var = IConsoleManager::Get().FindConsoleVariable(TEXT("r.ShaderPipelineCache.LogPSO")); + if(Var) + { + Var->Set( bEnable ? 1 : 0); + } + return !!Var; +} + +bool UFlibShaderPipelineCacheHelper::EnableSaveBoundPSOLog(bool bEnable) +{ + UE_LOG(LogHotPatcher,Display,TEXT("EnableSaveBoundPSOLog %s"),bEnable?TEXT("true"):TEXT("false")); + auto Var = IConsoleManager::Get().FindConsoleVariable(TEXT("r.ShaderPipelineCache.SaveBoundPSOLog")); + if(Var) + { + Var->Set( bEnable ? 1 : 0); + } + return !!Var; +} + +bool UFlibShaderPipelineCacheHelper::IsEnabledUsePSO() +{ + bool ret = false; + auto Var = IConsoleManager::Get().FindConsoleVariable(TEXT("r.ShaderPipelineCache.Enabled")); + if(Var) + { + ret = !!Var->GetInt(); + } + return ret; +} + +bool UFlibShaderPipelineCacheHelper::IsEnabledLogPSO() +{ + bool ret = false; + auto Var = IConsoleManager::Get().FindConsoleVariable(TEXT("r.ShaderPipelineCache.LogPSO")); + if(Var) + { + ret = !!Var->GetInt(); + } + return ret; +} + +bool UFlibShaderPipelineCacheHelper::IsEnabledSaveBoundPSOLog() +{ + bool ret = false; + auto Var = IConsoleManager::Get().FindConsoleVariable(TEXT("r.ShaderPipelineCache.SaveBoundPSOLog")); + if(Var) + { + ret = !!Var->GetInt(); + } + return ret; +} diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/HACK_PRIVATE_MEMBER_UTILS.hpp b/HotPatcher/Source/HotPatcherRuntime/Private/HACK_PRIVATE_MEMBER_UTILS.hpp new file mode 100644 index 0000000..b85d84e --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Private/HACK_PRIVATE_MEMBER_UTILS.hpp @@ -0,0 +1,119 @@ +using namespace std; +namespace Hacker +{ + template + struct PrivateMemberStealer + { + // define friend funtion GetPrivate,return class member pointer + friend typename Tag::MemType GetPrivate(Tag) { return M; } + }; +} + +#define DECL_HACK_PRIVATE_DATA(ClassName,DataType,DataName) namespace Hacker{\ + struct ClassName##_##DataName\ + {\ + typedef DataType ClassName::*MemType;\ + friend MemType GetPrivate(ClassName##_##DataName);\ + };\ + template struct PrivateMemberStealer;\ + } + +#define DECL_HACK_PRIVATE_STATIC_DATA(ClassName,DataType,DataName) namespace Hacker{\ + struct ClassName##_##DataName\ + {\ + typedef DataType* MemType;\ + friend MemType GetPrivate(ClassName##_##DataName);\ + };\ + template struct PrivateMemberStealer;\ + } + +#define DECL_HACK_PRIVATE_STATIC_FUNCTION(ClassName,MemberName,ReturnType,...) \ + namespace Hacker{\ + struct ClassName##_##MemberName \ + {\ + typedef ReturnType (*MemType)(__VA_ARGS__);\ + friend MemType GetPrivate(ClassName##_##MemberName);\ + };\ + template struct PrivateMemberStealer;\ + } + +#define DECL_HACK_PRIVATE_NOCONST_FUNCTION(ClassName,MemberName,ReturnType,...) \ + namespace Hacker{\ + struct ClassName##_##MemberName \ + {\ + typedef ReturnType (ClassName::*MemType)(__VA_ARGS__);\ + friend MemType GetPrivate(ClassName##_##MemberName);\ + };\ + template struct PrivateMemberStealer;\ + } +#define DECL_HACK_PRIVATE_CONST_FUNCTION(ClassName,MemberName,ReturnType,...) \ + namespace Hacker{\ + struct ClassName##_##MemberName \ + {\ + typedef ReturnType (ClassName::*MemType)(__VA_ARGS__)const;\ + friend MemType GetPrivate(ClassName##_##MemberName);\ + };\ + template struct PrivateMemberStealer;\ + } + +#define GET_VAR_PRIVATE_DATA_MEMBER(ClassInstancePointer,ClassName,DataName) ClassInstancePointer->*GetPrivate(::Hacker::ClassName##_##DataName()) +#define GET_REF_PRIVATE_DATA_MEMBER(RefName,ClassInstancePointer,ClassName,DataName) auto& RefName = ClassInstancePointer->*GetPrivate(::Hacker::ClassName##_##DataName()) +#define GET_PRIVATE_STATIC_DATA_MEMBER_PTR(PtrName,ClassName,DataName) auto PtrName = GetPrivate(::Hacker::ClassName##_##DataName()) +// using ADL found to ::Hacker::Getprivate +#define GET_PRIVATE_MEMBER_FUNCTION(ClassName,MemberName) GetPrivate(::Hacker::ClassName##_##MemberName()) +#define CALL_MEMBER_FUNCTION(ClassPointer,MemberFuncPointer,...) (ClassPointer->*MemberFuncPointer)(__VA_ARGS__) + +// class A{ +// public: +// A(int ivalp=0):ival{ivalp}{} +// +// static void printStaticIval2() +// { +// printf("%d\n",A::static_ival2); +// } +// private: +// int func(int ival)const +// { +// printf("A::func(int)\t%d\n",ival); +// printf("ival is %d\n",ival); +// return 0; +// } +// int ival; +// +// static int static_ival2; +// +// }; +// +// // private member data +// DECL_HACK_PRIVATE_DATA(A,int,ival) +// // private member function +// DECL_HACK_PRIVATE_CONST_FUNCTION(A, func, int, int) +// // private static member +// DECL_HACK_PRIVATE_STATIC_DATA(A,int,static_ival2) +// // private static member function +// DECL_HACK_PRIVATE_STATIC_FUNCTION(A,printStaticIval2,void); +// +// int A::static_ival2 = 666; +// +// int main() +// { +// A aobj(789); +// // get private non-static data member +// GET_REF_PRIVATE_DATA_MEMBER(ref_ival, &aobj, A, ival); +// ref_ival=456; +// std::cout<4 || ENGINE_MINOR_VERSION >=26 + FCoreDelegates::OnPakFileMounted2.AddLambda([this](const IPakFile& PakFile){this->OnMountPak(*PakFile.PakGetPakFilename(),0);}); +#endif + +#if ENGINE_MINOR_VERSION <=25 && ENGINE_MINOR_VERSION > 24 + FCoreDelegates::OnPakFileMounted.AddLambda([this](const TCHAR* Pak, const int32 ChunkID){this->OnMountPak(Pak,ChunkID);}); +#endif + +#if ENGINE_MAJOR_VERSION <=4 && ENGINE_MINOR_VERSION <= 24 + FCoreDelegates::PakFileMountedCallback.AddLambda([this](const TCHAR* Pak){this->OnMountPak(Pak,0);}); +#endif + FCoreDelegates::OnUnmountPak.BindUObject(this,&UMountListener::OnUnMountPak); +#if !WITH_EDITOR + FPakPlatformFile* PakFileMgr = (FPakPlatformFile*)(FPlatformFileManager::Get().FindPlatformFile(FPakPlatformFile::GetTypeName())); + TArray MountedPaks = UFlibPakHelper::GetAllMountedPaks(); + for(const auto& Pak:MountedPaks) + { +#if ENGINE_MAJOR_VERSION >4 || ENGINE_MINOR_VERSION > 24 + OnMountPak(*Pak,UFlibPakHelper::GetPakOrderByPakPath(Pak)); +#else + OnMountPak(*Pak); +#endif + } +#endif + } +} + +void UMountListener::OnMountPak(const TCHAR* PakFileName, int32 ChunkID) +{ + UE_LOG(LogMountListener,Log,TEXT("Pak %s is Mounted!"),PakFileName); + FPakMountInfo MountedPak; + MountedPak.Pak = PakFileName; + MountedPak.PakOrder = ChunkID; + MountedPak.PakOrder = UFlibPakHelper::GetPakOrderByPakPath(PakFileName); + PaksMap.Add(MountedPak.Pak,MountedPak); + OnMountPakDelegate.Broadcast(MountedPak); +} + +bool UMountListener::OnUnMountPak(const FString& Pak) +{ + PaksMap.Remove(Pak); + OnUnMountPakDelegate.Broadcast(Pak); + return true; +} + +TMap& UMountListener::GetMountedPaks() +{ + return PaksMap; +} \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/ReleaseParser/FReleasePakParser.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/ReleaseParser/FReleasePakParser.cpp new file mode 100644 index 0000000..9bcb89c --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Private/ReleaseParser/FReleasePakParser.cpp @@ -0,0 +1,64 @@ +#include "FReleasePakParser.h" +#include "HotPatcherLog.h" +#include "FlibPakHelper.h" +#include "FlibPatchParserHelper.h" + +void FReleasePakParser::Parser(TSharedPtr ParserConf, EHashCalculator HashCalculator) +{ + result.Platform = ParserConf->TargetPlatform; + FReleasePakFilesConf* Conf = (FReleasePakFilesConf*)ParserConf.Get(); + + TArray InnerPakFiles; + for(const auto& pakFile:Conf->PakFiles) + { + InnerPakFiles.Append(UFlibPakHelper::GetPakFileList(pakFile,Conf->AESKey)); + } + + for(const auto& MountFile:InnerPakFiles) + { + FString Extersion = FPaths::GetExtension(MountFile,true); + if(UFlibPatchParserHelper::GetCookedUassetExtensions().Contains(Extersion)) + { + if (!UFlibPatchParserHelper::GetUnCookUassetExtensions().Contains(Extersion)) + continue; + // is uasset or umap + FPatcherSpecifyAsset SpecifyAsset; + FString AbsFile = UFlibPatchParserHelper::AssetMountPathToAbs(MountFile); + FString LongPackageName; + if(FPackageName::TryConvertFilenameToLongPackageName(AbsFile,LongPackageName)) + { + FString PackagePath = UFlibAssetManageHelper::LongPackageNameToPackagePath(LongPackageName); + FPatcherSpecifyAsset currentAsset; + currentAsset.Asset = FSoftObjectPath(PackagePath); + currentAsset.bAnalysisAssetDependencies = false; + if(currentAsset.Asset.IsValid()) + { + result.Assets.Add(currentAsset); + } + } + else + { + UE_LOG(LogHotPatcher,Warning,TEXT("%s conv to abs path failed!"),*MountFile); + } + } + else + { + // not uasset + FExternFileInfo currentFile; + FString RelativePath = MountFile; + FString ModuleName; + FString ModuleAbsPath; + while(RelativePath.RemoveFromStart(TEXT("../"))); + + UFlibAssetManageHelper::GetModuleNameByRelativePath(RelativePath,ModuleName); + UFlibAssetManageHelper::GetEnableModuleAbsDir(ModuleName,ModuleAbsPath); + RelativePath.RemoveFromStart(ModuleName); + FString FinalFilePath = FPaths::Combine(ModuleAbsPath,RelativePath); + FPaths::NormalizeFilename(FinalFilePath); + currentFile.FilePath.FilePath = FinalFilePath; + currentFile.MountPath = MountFile; + currentFile.GenerateFileHash(HashCalculator); + result.ExternFiles.AddUnique(currentFile); + } + } +} diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/ReleaseParser/FReleasePakParser.h b/HotPatcher/Source/HotPatcherRuntime/Private/ReleaseParser/FReleasePakParser.h new file mode 100644 index 0000000..6a6af18 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Private/ReleaseParser/FReleasePakParser.h @@ -0,0 +1,8 @@ +#pragma once + +#include "IReleaseParser.h" + +struct FReleasePakParser : public IReleaseParser +{ + virtual void Parser(TSharedPtr ParserConf, EHashCalculator HashCalculator) override; +}; diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/ReleaseParser/FReleasePaklistParser.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/ReleaseParser/FReleasePaklistParser.cpp new file mode 100644 index 0000000..1574307 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Private/ReleaseParser/FReleasePaklistParser.cpp @@ -0,0 +1,94 @@ +#include "FReleasePaklistParser.h" + +#include "Misc/FileHelper.h" +#include "FlibPatchParserHelper.h" +#include "HotPatcherLog.h" +#include "Kismet/KismetStringLibrary.h" +// #include "Misc/PathViews.h" + + +FString NormalizePaklistPath(const FString& InStr) +{ + FString resultStr = InStr; + if(resultStr.StartsWith(TEXT("\""))) + { + resultStr.RemoveAt(0); + } + if(resultStr.EndsWith(TEXT("\""))) + { + resultStr.RemoveAt(resultStr.Len() - 1); + } + return resultStr; +}; + +struct FPakListAssetItem +{ + FString AbsPath; + FString MountPak; +}; + +FPakListAssetItem BreakPakListLine(const FString& PakListLine) +{ + FPakListAssetItem result; + TArray AssetPakCmd = UKismetStringLibrary::ParseIntoArray(PakListLine,TEXT("\" ")); + + FString AssetAbsPath = AssetPakCmd[0]; + FString AssetMountPath = AssetPakCmd[1]; + result.AbsPath = NormalizePaklistPath(AssetAbsPath); + result.MountPak = NormalizePaklistPath(AssetMountPath); + return result; +} + +void FReleasePaklistParser::Parser(TSharedPtr ParserConf, EHashCalculator HashCalculator) +{ + result.Platform = ParserConf->TargetPlatform; + FReleasePakListConf* Conf = (FReleasePakListConf*)ParserConf.Get(); + + for(const auto& ResponseFile:Conf->PakResponseFiles) + { + if (FPaths::FileExists(ResponseFile)) + { + TArray PakListContent; + if (FFileHelper::LoadFileToStringArray(PakListContent, *ResponseFile)) + { + for(const auto& PakListLine:PakListContent) + { + FPakListAssetItem LineItem = BreakPakListLine(PakListLine); + FString Extersion = FPaths::GetExtension(LineItem.MountPak,true); + if(UFlibPatchParserHelper::GetCookedUassetExtensions().Contains(Extersion)) + { + if(UFlibPatchParserHelper::GetUnCookUassetExtensions().Contains(Extersion)) + { + // is uasset + FPatcherSpecifyAsset SpecifyAsset; + FString AbsFile = UFlibPatchParserHelper::AssetMountPathToAbs(LineItem.MountPak); + FString LongPackageName; + if(FPackageName::TryConvertFilenameToLongPackageName(AbsFile,LongPackageName)) + { + FString PackagePath = UFlibAssetManageHelper::LongPackageNameToPackagePath(LongPackageName); + SpecifyAsset.Asset = FSoftObjectPath{PackagePath}; + SpecifyAsset.bAnalysisAssetDependencies = false; + SpecifyAsset.AssetRegistryDependencyTypes = {EAssetRegistryDependencyTypeEx::None}; + if(SpecifyAsset.Asset.IsValid()) + result.Assets.Add(SpecifyAsset); + } + else + { + UE_LOG(LogHotPatcher,Warning,TEXT("%s conv to abs path failed!"),*LineItem.MountPak); + } + } + } + else + { + // is not-uasset + FExternFileInfo ExFile; + ExFile.FilePath.FilePath = LineItem.AbsPath; + ExFile.MountPath = LineItem.MountPak; + ExFile.GenerateFileHash(HashCalculator); + result.ExternFiles.AddUnique(ExFile); + } + } + } + } + } +} diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/ReleaseParser/FReleasePaklistParser.h b/HotPatcher/Source/HotPatcherRuntime/Private/ReleaseParser/FReleasePaklistParser.h new file mode 100644 index 0000000..1b9537c --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Private/ReleaseParser/FReleasePaklistParser.h @@ -0,0 +1,8 @@ +#pragma once + +#include "IReleaseParser.h" + +struct FReleasePaklistParser : public IReleaseParser +{ + virtual void Parser(TSharedPtr ParserConf, EHashCalculator HashCalculator) override; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/ReleaseParser/IReleaseParser.h b/HotPatcher/Source/HotPatcherRuntime/Private/ReleaseParser/IReleaseParser.h new file mode 100644 index 0000000..5ddc29b --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Private/ReleaseParser/IReleaseParser.h @@ -0,0 +1,38 @@ +#pragma once + +#include "CoreMinimal.h" +#include "ETargetPlatform.h" +#include "FExternFileInfo.h" +#include "FPatcherSpecifyAsset.h" + +struct FReleaseParserConf +{ + ETargetPlatform TargetPlatform = ETargetPlatform::None; +}; + +struct FReleasePakListConf: public FReleaseParserConf +{ + TArray PakResponseFiles; +}; + +struct FReleasePakFilesConf : public FReleaseParserConf +{ + TArray PakFiles; + FString AESKey; +}; + +struct FReleaseParserResult +{ + ETargetPlatform Platform = ETargetPlatform::None; + TArray Assets; + TArray ExternFiles; +}; + +struct IReleaseParser +{ + virtual void Parser(TSharedPtr ParserConf, EHashCalculator HashCalculator)=0; + virtual const FReleaseParserResult& GetParserResult()const {return result;}; + virtual ~IReleaseParser(){} +protected: + FReleaseParserResult result; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Private/TargetPlatformRegister.cpp b/HotPatcher/Source/HotPatcherRuntime/Private/TargetPlatformRegister.cpp new file mode 100644 index 0000000..9c42900 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Private/TargetPlatformRegister.cpp @@ -0,0 +1,34 @@ +#include "TargetPlatformRegister.h" + +#include "ETargetPlatform.h" +#include "HotPatcherTemplateHelper.hpp" +#include "HotPatcherLog.h" + +#if WITH_EDITOR +#include "Interfaces/ITargetPlatform.h" +#include "Interfaces/ITargetPlatformManagerModule.h" +#endif + +UTargetPlatformRegister::UTargetPlatformRegister(const FObjectInitializer& Initializer):Super(Initializer) +{ + TArray AppendPlatformEnums; + +#if WITH_EDITOR + TArray RealPlatformEnums; + ITargetPlatformManagerModule& TPM = GetTargetPlatformManagerRef(); + const TArray& TargetPlatforms = TPM.GetTargetPlatforms(); + UE_LOG(LogHotPatcher,Display,TEXT("TargetPlatformRegister:")); + for (ITargetPlatform *TargetPlatformIns : TargetPlatforms) + { + FString PlatformName = TargetPlatformIns->PlatformName(); + if(!PlatformName.IsEmpty()) + { + UE_LOG(LogHotPatcher,Display,TEXT("\t%s"),*PlatformName); + RealPlatformEnums.AddUnique(PlatformName); + } + } + AppendPlatformEnums = RealPlatformEnums; +#endif + + TArray> EnumNames = THotPatcherTemplateHelper::AppendEnumeraters(AppendPlatformEnums); +} \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/AssetManager/FAssetDependenciesDetail.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/AssetManager/FAssetDependenciesDetail.h new file mode 100644 index 0000000..a3c28f1 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/AssetManager/FAssetDependenciesDetail.h @@ -0,0 +1,28 @@ +#pragma once + +//project header +#include "FAssetDetail.h" + +//engine header +#include "CoreMinimal.h" +#include "FAssetDependenciesDetail.generated.h" + +USTRUCT(BlueprintType) +struct HOTPATCHERRUNTIME_API FAssetDependenciesDetail +{ + GENERATED_USTRUCT_BODY() + + FAssetDependenciesDetail() = default; + FAssetDependenciesDetail(const FAssetDependenciesDetail&) = default; + FAssetDependenciesDetail& operator=(const FAssetDependenciesDetail&) = default; + FORCEINLINE FAssetDependenciesDetail(const FString& InModuleCategory, const TMap& InDependAssetDetails) + : ModuleCategory(InModuleCategory), AssetDependencyDetails(InDependAssetDetails) + {} + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FString ModuleCategory; + //UPROPERTY(EditAnywhere, BlueprintReadWrite) + // TArray mDependAsset; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TMap AssetDependencyDetails; +}; diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/AssetManager/FAssetDependenciesInfo.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/AssetManager/FAssetDependenciesInfo.h new file mode 100644 index 0000000..b2180ed --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/AssetManager/FAssetDependenciesInfo.h @@ -0,0 +1,27 @@ +#pragma once + +#include "Containers/Map.h" +#include "AssetManager/FAssetDependenciesDetail.h" +#include "CoreMinimal.h" +#include "FAssetDependenciesInfo.generated.h" + +USTRUCT(BlueprintType) +struct HOTPATCHERRUNTIME_API FAssetDependenciesInfo +{ + GENERATED_USTRUCT_BODY() + FAssetDependenciesInfo()=default; + FAssetDependenciesInfo(const FAssetDependenciesInfo&)=default; + //UPROPERTY(EditAnywhere, BlueprintReadWrite) + //FString mAssetRef; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TMap AssetsDependenciesMap; + + void AddAssetsDetail(const FAssetDetail& AssetDetail); + bool HasAsset(const FString& InAssetPackageName)const; + TArray GetAssetDetails()const; + bool GetAssetDetailByPackageName(const FString& InAssetPackageName,FAssetDetail& OutDetail)const; + TArray GetAssetLongPackageNames()const; + void RemoveAssetDetail(const FAssetDetail& AssetDetail); + void RemoveAssetDetail(const FString& LongPackageName); +}; + diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/AssetManager/FAssetDetail.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/AssetManager/FAssetDetail.h new file mode 100644 index 0000000..8db2831 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/AssetManager/FAssetDetail.h @@ -0,0 +1,43 @@ +#pragma once + +#include "AssetData.h" +// #include "FlibAssetManageHelper.h" +#include "UObject/NameTypes.h" +#include "CoreMinimal.h" +#include "FAssetDetail.generated.h" + +USTRUCT(BlueprintType) +struct HOTPATCHERRUNTIME_API FAssetDetail +{ + GENERATED_USTRUCT_BODY() + + FAssetDetail() = default; + FAssetDetail(const FAssetDetail&) = default; + FAssetDetail& operator=(const FAssetDetail&) = default; + FORCEINLINE FAssetDetail(const FName& InAssetPackagePath, const FName& InAsetType,const FName& InGuid) + : PackagePath(InAssetPackagePath), AssetType(InAsetType), Guid(InGuid){} + FORCEINLINE FAssetDetail(const FString& InAssetPackagePath, const FString& InAsetType,const FString& InGuid) + : FAssetDetail(FName(*InAssetPackagePath),FName(*InAsetType),FName(*InGuid)){} + + bool operator==(const FAssetDetail& InRight)const + { + bool bSamePackageName = PackagePath == InRight.PackagePath; + bool bSameAssetType = AssetType == InRight.AssetType; + bool bSameGUID = Guid == InRight.Guid; + + return bSamePackageName && bSameAssetType && bSameGUID; + } + FORCEINLINE bool IsValid()const + { + return !PackagePath.IsNone() && !AssetType.IsNone() && !Guid.IsNone(); + } + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FName PackagePath; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FName AssetType; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FName Guid; + +}; + + diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/AssetManager/FFileArrayDirectoryVisitor.hpp b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/AssetManager/FFileArrayDirectoryVisitor.hpp new file mode 100644 index 0000000..43eacb2 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/AssetManager/FFileArrayDirectoryVisitor.hpp @@ -0,0 +1,23 @@ +#pragma once +#include "CoreMinimal.h" +#include "GenericPlatform/GenericPlatformFile.h" + +class HOTPATCHERRUNTIME_API FFileArrayDirectoryVisitor : public IPlatformFile::FDirectoryVisitor +{ +public: + virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override + { + if (bIsDirectory) + { + Directories.Add(FilenameOrDirectory); + } + else + { + Files.Add(FilenameOrDirectory); + } + return true; + } + + TArray Directories; + TArray Files; +}; diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/BaseTypes.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/BaseTypes.h new file mode 100644 index 0000000..f01b824 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/BaseTypes.h @@ -0,0 +1,17 @@ +#pragma once + +#include "CoreMinimal.h" + +#define AS_PROJECTDIR_MARK TEXT("[PROJECTDIR]") +#define AS_PLUGINDIR_MARK TEXT("[PLUGINDIR]") + +struct FEncryptSetting +{ + // -encryptindex + bool bEncryptIndex = false; + bool bEncryptAllAssetFiles = false; + bool bEncryptUAssetFiles = false; + bool bEncryptIniFiles = false; + // sign pak + bool bSign = false; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/EPatcherPhase.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/EPatcherPhase.h new file mode 100644 index 0000000..72b704d --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/EPatcherPhase.h @@ -0,0 +1,30 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Misc/EnumRange.h" +// #include "EPatcherPhase.generated.h" +// +// +// UENUM(BlueprintType) +// enum class EPatcherPhase : uint8 +// { +// PRE_PATCHER, +// BASE_VERSION, +// NEW_VERSION, +// PRE_DIFF, +// DIFF, +// POST_DIFF, +// PARSE_CHUNKS, +// PRE_COOKING, +// COOKING, +// POST_COOKING, +// GENERATE_METADATA, +// PRE_GENERATE_PAK +// GENERATE_PAK, +// POST_GENERATE_PAK, +// CHECK_ASSETS_COOKED, +// POST_PATCHER, +// Count UMETA(Hidden) +// }; +// +// ENUM_RANGE_BY_COUNT(ETargetPlatform, ETargetPlatform::Count); diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/ETargetPlatform.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/ETargetPlatform.h new file mode 100644 index 0000000..e95b012 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/ETargetPlatform.h @@ -0,0 +1,95 @@ +#pragma once + +#include "Templates/HotPatcherTemplateHelper.hpp" + +#include "CoreMinimal.h" +#include "Misc/EnumRange.h" +#include "ETargetPlatform.generated.h" + + +UENUM(BlueprintType) +enum class ETargetPlatform : uint8 +{ + None, + AllPlatforms, + Count UMETA(Hidden) +}; +ENUM_RANGE_BY_COUNT(ETargetPlatform, ETargetPlatform::Count); + +DECAL_GETCPPTYPENAME_SPECIAL(ETargetPlatform) + +// static TArray AppendPlatformEnums = { +// #if ENGINE_MAJOR_VERSION > 4 +// TEXT("Android"), +// TEXT("Android_ASTC"), +// TEXT("Android_DXT"), +// TEXT("Android_ETC2"), +// TEXT("AndroidClient"), +// TEXT("Android_ASTCClient"), +// TEXT("Android_DXTClient"), +// TEXT("Android_ETC2Client"), +// TEXT("Android_Multi"), +// TEXT("Android_MultiClient"), +// TEXT("IOS"), +// TEXT("IOSClient"), +// TEXT("Linux"), +// TEXT("LinuxEditor"), +// TEXT("LinuxServer"), +// TEXT("LinuxClient"), +// TEXT("LinuxAArch64"), +// TEXT("LinuxAArch64Server"), +// TEXT("LinuxAArch64Client"), +// TEXT("Lumin"), +// TEXT("LuminClient"), +// TEXT("Mac"), +// TEXT("MacEditor"), +// TEXT("MacServer"), +// TEXT("MacClient"), +// TEXT("TVOS"), +// TEXT("TVOSClient"), +// TEXT("Windows"), +// TEXT("WindowsEditor"), +// TEXT("WindowsServer"), +// TEXT("WindowsClient") +// #else +// // for UE4 +// TEXT("AllDesktop"), +// TEXT("MacClient"), +// TEXT("MacNoEditor"), +// TEXT("MacServer"), +// TEXT("Mac"), +// TEXT("WindowsClient"), +// TEXT("WindowsNoEditor"), +// TEXT("WindowsServer"), +// TEXT("Windows"), +// TEXT("Android"), +// TEXT("Android_ASTC"), +// TEXT("Android_ATC"), +// TEXT("Android_DXT"), +// TEXT("Android_ETC1"), +// TEXT("Android_ETC1a"), +// TEXT("Android_ETC2"), +// TEXT("Android_PVRTC"), +// TEXT("AndroidClient"), +// TEXT("Android_ASTCClient"), +// TEXT("Android_ATCClient"), +// TEXT("Android_DXTClient"), +// TEXT("Android_ETC1Client"), +// TEXT("Android_ETC1aClient"), +// TEXT("Android_ETC2Client"), +// TEXT("Android_PVRTCClient"), +// TEXT("Android_Multi"), +// TEXT("Android_MultiClient"), +// TEXT("HTML5"), +// TEXT("IOSClient"), +// TEXT("IOS"), +// TEXT("TVOSClient"), +// TEXT("TVOS"), +// TEXT("LinuxClient"), +// TEXT("LinuxNoEditor"), +// TEXT("LinuxServer"), +// TEXT("Linux"), +// TEXT("Lumin"), +// TEXT("LuminClient") +// #endif +// }; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FAssetRegistryOptions.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FAssetRegistryOptions.h new file mode 100644 index 0000000..43a244f --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FAssetRegistryOptions.h @@ -0,0 +1,47 @@ +#pragma once +#include "BaseTypes.h" + +// engine header +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "UObject/Object.h" +#include "Engine/EngineTypes.h" +#include "FAssetRegistryOptions.generated.h" + +UENUM(BlueprintType) +enum class EAssetRegistryRule : uint8 +{ + PATCH, + PER_CHUNK, + CUSTOM +}; + + +USTRUCT(BlueprintType) +struct HOTPATCHERRUNTIME_API FAssetRegistryOptions +{ + GENERATED_BODY() + FAssetRegistryOptions() + { + AssetRegistryMountPointRegular = FString::Printf(TEXT("%s/AssetRegistry"),AS_PROJECTDIR_MARK); + AssetRegistryNameRegular = FString::Printf(TEXT("[CHUNK_NAME]_AssetRegistry.bin")); + } + FString GetAssetRegistryNameRegular(const FString& ChunkName)const + { + return AssetRegistryNameRegular.Replace(TEXT("[CHUNK_NAME]"),*ChunkName); + } + FString GetAssetRegistryMountPointRegular()const { return AssetRegistryMountPointRegular; } + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bSerializeAssetRegistry = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bSerializeAssetRegistryManifest = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FString AssetRegistryMountPointRegular; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + EAssetRegistryRule AssetRegistryRule = EAssetRegistryRule::PATCH; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bCustomAssetRegistryName = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite,meta=(EditCondition="bCustomAssetRegistryName")) + FString AssetRegistryNameRegular; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FAssetScanConfig.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FAssetScanConfig.h new file mode 100644 index 0000000..8f44d84 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FAssetScanConfig.h @@ -0,0 +1,58 @@ +#pragma once +// project header +#include "FPatcherSpecifyAsset.h" +#include "FExternFileInfo.h" +#include "ETargetPlatform.h" +#include "FPlatformExternFiles.h" +#include "FPlatformExternAssets.h" + +// engine header +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +#include "FAssetScanConfig.generated.h" + +USTRUCT(BlueprintType) +struct HOTPATCHERRUNTIME_API FAssetScanConfig +{ + GENERATED_USTRUCT_BODY() + +public: + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bPackageTracker = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite,meta = (RelativeToGameContentDir, LongPackageName)) + TArray AssetIncludeFilters; + // Ignore directories in AssetIncludeFilters + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (RelativeToGameContentDir, LongPackageName)) + TArray AssetIgnoreFilters; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bIncludeHasRefAssetsOnly = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bAnalysisFilterDependencies=true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TArray AssetRegistryDependencyTypes; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TArray IncludeSpecifyAssets; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bRecursiveWidgetTree = true; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bAnalysisMaterialInstance = true; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bSupportWorldComposition = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bForceSkipContent = true; + // force exclude asset folder e.g. Exclude editor content when cooking in Project Settings + UPROPERTY(EditAnywhere, BlueprintReadWrite,meta = (RelativeToGameContentDir, LongPackageName, EditCondition="bForceSkipContent")) + TArray ForceSkipContentRules; + UPROPERTY(EditAnywhere, BlueprintReadWrite,meta = (EditCondition="bForceSkipContent")) + TArray ForceSkipAssets; + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditCondition="bForceSkipContent")) + TArray ForceSkipClasses; + + bool IsMatchForceSkip(const FSoftObjectPath& ObjectPath,FString& OutReason); + +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FBinariesPatchConfig.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FBinariesPatchConfig.h new file mode 100644 index 0000000..b00ec7a --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FBinariesPatchConfig.h @@ -0,0 +1,87 @@ +#pragma once +#include "ETargetPlatform.h" +#include "FPlatformBasePak.h" +#include "BinariesPatchFeature.h" +#include "FPakEncryptionKeys.h" + +// engine header +#include "CoreMinimal.h" +#include "Engine/EngineTypes.h" +#include "HAL/FileManager.h" +#include "FBinariesPatchConfig.generated.h" + +struct FPakCommandItem +{ + FString AssetAbsPath; + FString AssetMountPath; +}; +UENUM(BlueprintType) +enum class EMatchRule:uint8 +{ + None, + MATCH, + IGNORE +}; + +UENUM(BlueprintType) +enum class EMatchOperator:uint8 +{ + None, + GREAT_THAN, + LESS_THAN, + EQUAL +}; + +USTRUCT(BlueprintType) +struct FMatchRule +{ + GENERATED_BODY() + // match or ignore + UPROPERTY(EditAnywhere) + EMatchRule Rule = EMatchRule::None; + + // grate/less/equal + UPROPERTY(EditAnywhere) + EMatchOperator Operator = EMatchOperator::None; + + // uint kb + UPROPERTY(EditAnywhere,meta=(EditCondition="Operator!=EMatchOperator::None")) + float Size = 100; + // match file Formats. etc .ini/.lua, if it is empty match everything + UPROPERTY(EditAnywhere) + TArray Formaters; + UPROPERTY(EditAnywhere) + TArray AssetTypes; +}; + +USTRUCT(BlueprintType) +struct HOTPATCHERRUNTIME_API FBinariesPatchConfig +{ + GENERATED_BODY(); +public: + FORCEINLINE FPakEncryptSettings GetEncryptSettings()const{ return EncryptSettings; } + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BinariesPatch") + EBinariesPatchFeature BinariesPatchType = EBinariesPatchFeature::None; + // UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BinariesPatch") + FDirectoryPath OldCookedDir; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BinariesPatch") + FPakEncryptSettings EncryptSettings; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BinariesPatch") + TArray BaseVersionPaks; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BinariesPatch") + TArray MatchRules; + // etc .ini/.lua + // UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BinariesPatch") + // TArray IgnoreFileRules; + bool IsMatchIgnoreRules(const FPakCommandItem& File); + + // FORCEINLINE TArray GetBinariesPatchIgnoreFileRules()const {return IgnoreFileRules;} + FORCEINLINE TArray GetMatchRules()const{ return MatchRules; } + FORCEINLINE TArray GetBaseVersionPaks()const {return BaseVersionPaks;}; + FString GetBinariesPatchFeatureName() const; + FString GetOldCookedDir() const; + FString GetBasePakExtractCryptoJson() const; + TArray GetBaseVersionPakByPlatform(ETargetPlatform Platform); + +}; diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FChunkInfo.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FChunkInfo.h new file mode 100644 index 0000000..68ce4d1 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FChunkInfo.h @@ -0,0 +1,211 @@ +#pragma once +#include "HotPatcherBaseTypes.h" +#include "FExternFileInfo.h" +#include "FExternDirectoryInfo.h" +#include "FPatcherSpecifyAsset.h" +#include "FPlatformExternAssets.h" +#include "BaseTypes/AssetManager/FAssetDependenciesInfo.h" +#include "FlibAssetManageHelper.h" +#include "FPlatformExternFiles.h" +#include "ETargetPlatform.h" +#include "BaseTypes/FCookShaderOptions.h" +#include "BaseTypes/FAssetRegistryOptions.h" + +// engine header +#include "CoreMinimal.h" + +#include "Engine/EngineTypes.h" +#include "FChunkInfo.generated.h" + + +UENUM(BlueprintType) +enum class EMonolithicPathMode :uint8 +{ + MountPath, + PackagePath +}; + + +// 引擎的数据和ini等配置文件 +USTRUCT(BlueprintType) +struct HOTPATCHERRUNTIME_API FPakInternalInfo +{ + GENERATED_USTRUCT_BODY() +public: + FORCEINLINE bool HasValidAssets()const + { + return bIncludeAssetRegistry || bIncludeGlobalShaderCache || bIncludeShaderBytecode || bIncludeEngineIni || bIncludePluginIni || bIncludeProjectIni; + } + + // UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cooked") + bool bIncludeAssetRegistry = false; + // UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cooked") + bool bIncludeGlobalShaderCache = false; + // UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cooked") + bool bIncludeShaderBytecode = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ini") + bool bIncludeEngineIni = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ini") + bool bIncludePluginIni = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ini") + bool bIncludeProjectIni = false; +}; + + +USTRUCT(BlueprintType) +struct HOTPATCHERRUNTIME_API FPakCommand +{ + GENERATED_USTRUCT_BODY() +public: + FPakCommand() = default; + FPakCommand(const FString& InMountPath, const TArray& InCommands) + :MountPath(InMountPath),PakCommands(InCommands){} + bool operator==(const FPakCommand& PakCmd)const + { + return GetMountPath() == PakCmd.GetMountPath() && GetPakCommands() == PakCmd.GetPakCommands(); + } + + const FString& GetMountPath()const{ return MountPath; } + const TArray& GetPakCommands()const{ return PakCommands; } + const TArray& GetIoStoreCommands()const{ return IoStoreCommands; } +public: + UPROPERTY(EditAnywhere) + FString ChunkName; + UPROPERTY(EditAnywhere) + FString MountPath; + UPROPERTY(EditAnywhere) + FString AssetPackage; + UPROPERTY(EditAnywhere) + TArray PakCommands; + UPROPERTY(EditAnywhere) + TArray IoStoreCommands; + + EPatchAssetType Type = EPatchAssetType::None; +}; + +USTRUCT(BlueprintType) +struct HOTPATCHERRUNTIME_API FPakFileProxy +{ + GENERATED_USTRUCT_BODY() +public: + UPROPERTY(EditAnywhere) + FString ChunkStoreName; + UPROPERTY(EditAnywhere) + ETargetPlatform Platform = ETargetPlatform::None; + UPROPERTY(EditAnywhere) + FString StorageDirectory; + // UPROPERTY(EditAnywhere) + // FString PakCommandSavePath; + // UPROPERTY(EditAnywhere) + // FString PakSavePath; + UPROPERTY(EditAnywhere) + TArray PakCommands; + UPROPERTY(EditAnywhere) + TArray IoStoreCommands; +}; + +USTRUCT(BlueprintType) +struct HOTPATCHERRUNTIME_API FChunkInfo +{ + GENERATED_USTRUCT_BODY() +public: + FChunkInfo() + { + AssetRegistryDependencyTypes = TArray{ EAssetRegistryDependencyTypeEx::Packages }; + } + UPROPERTY(EditAnywhere,BlueprintReadWrite) + FString ChunkName; + UPROPERTY(EditAnywhere,BlueprintReadWrite) + FString ChunkAliasName; + UPROPERTY(EditAnywhere,BlueprintReadWrite) + int32 Priority = -1; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bMonolithic = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite,meta=(EditCondition="bMonolithic")) + EMonolithicPathMode MonolithicPathMode = EMonolithicPathMode::MountPath; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bOutputDebugInfo = false; + // UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bStorageUnrealPakList = true; + // UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bStorageIoStorePakList = true; + UPROPERTY(EditAnywhere,BlueprintReadWrite, Category = "Assets", meta = (RelativeToGameContentDir, LongPackageName)) + TArray AssetIncludeFilters; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Assets", meta = (RelativeToGameContentDir, LongPackageName)) + TArray AssetIgnoreFilters; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bAnalysisFilterDependencies = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite,meta=(EditCondition="bAnalysisFilterDependencies")) + TArray AssetRegistryDependencyTypes; + UPROPERTY(EditAnywhere,BlueprintReadWrite, Category = "Assets") + TArray IncludeSpecifyAssets; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Assets") + bool bForceSkipContent = false; + // force exclude asset folder e.g. Exclude editor content when cooking in Project Settings + UPROPERTY(EditAnywhere, BlueprintReadWrite,Category = "Assets",meta = (RelativeToGameContentDir, LongPackageName, EditCondition="bForceSkipContent")) + TArray ForceSkipContentRules; + UPROPERTY(EditAnywhere, BlueprintReadWrite,Category = "Assets",meta = (EditCondition="bForceSkipContent")) + TArray ForceSkipAssets; + // UPROPERTY(EditAnywhere, BlueprintReadWrite,Category = "Assets",meta = (EditCondition="bForceSkipContent")) + TArray ForceSkipClasses; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "External") + TArray AddExternAssetsToPlatform; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Internal", meta = (EditCondition = "!bMonolithic")) + FPakInternalInfo InternalFiles; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Shader") + FCookShaderOptions CookShaderOptions; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AssetRegistry") + FAssetRegistryOptions AssetRegistryOptions; + + FORCEINLINE FCookShaderOptions GetCookShaderOptions()const {return CookShaderOptions;} + FString GetShaderLibraryName() const; + + + + TArray GetManagedAssets()const; + + TArray& GetPakFileProxys(){ return PakFileProxys; } + const TArray& GetPakFileProxys()const { return PakFileProxys; } +private: + TArray PakFileProxys; +}; + + +USTRUCT(BlueprintType) +struct HOTPATCHERRUNTIME_API FChunkPakCommand +{ + GENERATED_USTRUCT_BODY() +public: + TArray AsssetPakCommands; + TArray AsssetIoStoreCommands; + TArray ExternFilePakCommands; + TArray InternalPakCommands; +}; + +USTRUCT(BlueprintType) +struct HOTPATCHERRUNTIME_API FChunkAssetDescribe +{ + GENERATED_USTRUCT_BODY() +public: + FAssetDependenciesInfo Assets; + FAssetDependenciesInfo AddAssets; + FAssetDependenciesInfo ModifyAssets; + TMap AllPlatformExFiles; + FPakInternalInfo InternalFiles; // general platform + + bool HasValidAssets()const; + TArray GetAssetsDetail()const; + TArray GetAssetsStrings()const; + TArray GetExFilesByPlatform(ETargetPlatform Platform)const; + TArray GetExternalFileNames(ETargetPlatform Platform)const; + TArray GetInternalFileNames()const; + FChunkInfo AsChunkInfo(const FString& ChunkName); + + FORCEINLINE FPakInternalInfo GetInternalInfo()const{return InternalFiles;} + +}; + + diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FCookShaderOptions.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FCookShaderOptions.h new file mode 100644 index 0000000..0b81217 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FCookShaderOptions.h @@ -0,0 +1,39 @@ +#pragma once +#include "BaseTypes.h" + +#include "CoreMinimal.h" +#include "FCookShaderOptions.generated.h" + +UENUM(BlueprintType) +enum class EShaderLibNameRule : uint8 +{ + CHUNK_NAME, + PROJECT_NAME, + CUSTOM +}; + + +USTRUCT(BlueprintType) +struct FCookShaderOptions +{ + GENERATED_BODY() + FCookShaderOptions() + { + ShaderLibMountPointRegular = FString::Printf(TEXT("%s/ShaderLibs"),AS_PROJECTDIR_MARK); + } + FString GetShaderLibMountPointRegular()const { return ShaderLibMountPointRegular; } + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bSharedShaderLibrary = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bNativeShader = false; + // metallib and metalmap to pak? + bool bNativeShaderToPak = false; + // if name is StartContent to ShaderArchive-StarterContent-PCD3D_SM5.ushaderbytecode + UPROPERTY(EditAnywhere, BlueprintReadWrite) + EShaderLibNameRule ShaderNameRule = EShaderLibNameRule::CHUNK_NAME; + UPROPERTY(EditAnywhere, BlueprintReadWrite,meta=(EditCondition="ShaderNameRule==EShaderLibNameRule::CUSTOM")) + FString CustomShaderName; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FString ShaderLibMountPointRegular; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FCookerConfig.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FCookerConfig.h new file mode 100644 index 0000000..1387c2d --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FCookerConfig.h @@ -0,0 +1,30 @@ +#pragma once +// engine header +#include "CoreMinimal.h" +#include "Engine/EngineTypes.h" +#include "FCookerConfig.generated.h" + +USTRUCT() +struct FCookerConfig +{ + GENERATED_USTRUCT_BODY() +public: + UPROPERTY(EditAnywhere) + FString EngineBin; + UPROPERTY(EditAnywhere) + FString ProjectPath; + UPROPERTY(EditAnywhere) + FString EngineParams; + UPROPERTY(EditAnywhere) + TArray CookPlatforms; + UPROPERTY(EditAnywhere) + bool bCookAllMap = false; + UPROPERTY(EditAnywhere) + TArray CookMaps; + UPROPERTY(EditAnywhere) + TArray CookFilter; + UPROPERTY(EditAnywhere) + TArray CookSettings; + UPROPERTY(EditAnywhere) + FString Options; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FExternDirectoryInfo.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FExternDirectoryInfo.h new file mode 100644 index 0000000..078f61e --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FExternDirectoryInfo.h @@ -0,0 +1,32 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Engine/EngineTypes.h" +#include "FExternDirectoryInfo.generated.h" + +USTRUCT(BlueprintType) +struct FExternDirectoryInfo +{ + GENERATED_USTRUCT_BODY() + +public: + FORCEINLINE FExternDirectoryInfo():MountPoint(FPaths::Combine(TEXT("../../.."),FApp::GetProjectName())){} + FExternDirectoryInfo(const FExternDirectoryInfo&) = default; + FExternDirectoryInfo& operator=(const FExternDirectoryInfo&) = default; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BaseVersion") + FDirectoryPath DirectoryPath; + UPROPERTY(EditAnywhere,BlueprintReadWrite) + FString MountPoint = TEXT("../../../"); + UPROPERTY(EditAnywhere,BlueprintReadWrite) + bool bWildcard = false; + UPROPERTY(EditAnywhere,BlueprintReadWrite,meta=(EditCondition="bWildcard")) + FString WildcardStr; + + bool operator==(const FExternDirectoryInfo& Right)const + { + bool bIsSamePath = (DirectoryPath.Path == Right.DirectoryPath.Path); + bool bIsSameMountPath = (MountPoint == Right.MountPoint); + return bIsSamePath && bIsSameMountPath; + } +}; diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FExternFileInfo.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FExternFileInfo.h new file mode 100644 index 0000000..e0822c5 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FExternFileInfo.h @@ -0,0 +1,61 @@ +#pragma once +#include "HotPatcherBaseTypes.h" +// engine header +#include "Misc/App.h" +#include "Misc/Paths.h" +#include "Misc/SecureHash.h" +#include "CoreMinimal.h" +#include "Engine/EngineTypes.h" +#include "FExternFileInfo.generated.h" + + +UENUM() +enum class EHashCalculator +{ + NoHash, + MD5, + SHA1 +}; + +USTRUCT(BlueprintType) +struct FExternFileInfo +{ + GENERATED_USTRUCT_BODY() + +public: + FORCEINLINE FExternFileInfo():MountPath(FPaths::Combine(TEXT("../../.."),FApp::GetProjectName())){} + FExternFileInfo(const FExternFileInfo&) = default; + FExternFileInfo& operator=(const FExternFileInfo&) = default; + + FString GenerateFileHash(EHashCalculator HashCalculator = EHashCalculator::MD5); + FString GetFileHash(EHashCalculator HashCalculator = EHashCalculator::MD5)const; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BaseVersion", meta = (RelativeToGameContentDir)) + FFilePath FilePath; + UPROPERTY(EditAnywhere,BlueprintReadWrite) + FString MountPath = TEXT("../../../"); + UPROPERTY() + FString FileHash; + + EPatchAssetType Type = EPatchAssetType::None; + + bool operator==(const FExternFileInfo& Right)const + { + return MountPath == Right.MountPath; + } + + // ignore FilePath abs path (only mount path and filehash) + bool IsSameMount(const FExternFileInfo& Right)const + { + bool IsSameHash = (FileHash == Right.FileHash); + return (*this == Right) && IsSameHash; + } + + bool IsAbsSame(const FExternFileInfo& Right)const + { + bool bIsSamePath = (FilePath.FilePath == Right.FilePath.FilePath); + bool IsSameHash = (FileHash == Right.FileHash); + return (*this == Right) && bIsSamePath && IsSameHash; + } + +}; diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FHotPatcherAssetDependency.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FHotPatcherAssetDependency.h new file mode 100644 index 0000000..4e6cd0d --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FHotPatcherAssetDependency.h @@ -0,0 +1,21 @@ +#pragma once +// project header +#include "BaseTypes/AssetManager/FAssetDetail.h" + +// engine header +#include "CoreMinimal.h" +#include "Engine/EngineTypes.h" +#include "FHotPatcherAssetDependency.generated.h" + +USTRUCT(BlueprintType) +struct FHotPatcherAssetDependency +{ + GENERATED_USTRUCT_BODY() +public: + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FAssetDetail Asset; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TArray AssetReference; + UPROPERTY(EditAnywhere,BlueprintReadWrite) + TArray AssetDependency; +}; diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FHotPatcherVersion.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FHotPatcherVersion.h new file mode 100644 index 0000000..c836614 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FHotPatcherVersion.h @@ -0,0 +1,48 @@ +#pragma once +// project header +#include "AssetManager/FAssetDependenciesInfo.h" +#include "FPatcherSpecifyAsset.h" +#include "FExternFileInfo.h" +#include "ETargetPlatform.h" +#include "FPlatformExternFiles.h" +#include "FPlatformExternAssets.h" + +// engine header +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +#include "FHotPatcherVersion.generated.h" + + + +USTRUCT(BlueprintType) +struct FHotPatcherVersion +{ + GENERATED_USTRUCT_BODY() + +public: + FHotPatcherVersion()=default; + + UPROPERTY(EditAnywhere,BlueprintReadWrite) + FString VersionId; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FString BaseVersionId; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FString Date; + // UPROPERTY(EditAnywhere, BlueprintReadWrite) + TArray IncludeFilter; + // UPROPERTY(EditAnywhere, BlueprintReadWrite) + TArray IgnoreFilter; + // UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bIncludeHasRefAssetsOnly; + // UPROPERTY(EditAnywhere, BlueprintReadWrite) + TArray AssetRegistryDependencyTypes; + // UPROPERTY(EditAnywhere, BlueprintReadWrite) + TArray IncludeSpecifyAssets; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FAssetDependenciesInfo AssetInfo; + // UPROPERTY(EditAnywhere, BlueprintReadWrite) + // TMap ExternalFiles; + UPROPERTY(EditAnywhere,BlueprintReadWrite) + TMap PlatformAssets; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FIoStoreSettings.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FIoStoreSettings.h new file mode 100644 index 0000000..6a2ac30 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FIoStoreSettings.h @@ -0,0 +1,59 @@ +#pragma once +// project +#include "ETargetPlatform.h" + +// engine header +#include "CoreMinimal.h" +#include "Engine/EngineTypes.h" +#include "FIoStoreSettings.generated.h" + +// -Output=E:\UnrealProjects\StarterContent\Package\DLC2\WindowsNoEditor\StarterContent\Content\Paks\StarterContent-WindowsNoEditor_0_P.utoc +// -ContainerName=StarterContent +// -PatchSource=E:\UnrealProjects\StarterContent\Releases\1.0\WindowsNoEditor\StarterContent-WindowsNoEditor*.utoc +// -GenerateDiffPatch +// -ResponseFile="C:\Users\visionsmile\AppData\Roaming\Unreal Engine\AutomationTool\Logs\E+UnrealEngine+Launcher+UE_4.26\PakListIoStore_StarterContent.txt" + +USTRUCT(BlueprintType) +struct FIoStorePlatformContainers +{ + GENERATED_USTRUCT_BODY() + // Saved/StagedBuilds/Windows + // Saved/StagedBuilds/Android_ASTC + // │ Manifest_NonUFSFiles_Win64.txt + // │ ThirdPerson_UE5.exe + // ├─Engine + // └─ThirdPerson_UE5 + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FDirectoryPath BasePackageStagedRootDir; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bGenerateDiffPatch = false; + + // global.utoc file + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FFilePath GlobalContainersOverride; + // -PatchSource=E:\UnrealProjects\StarterContent\Releases\1.0\WindowsNoEditor\StarterContent-WindowsNoEditor*.utoc -GenerateDiffPatch + UPROPERTY(EditAnywhere,BlueprintReadWrite,meta=(EditCondition="bGenerateDiffPatch")) + FFilePath PatchSourceOverride; +}; + +USTRUCT(BlueprintType) +struct FIoStoreSettings +{ + GENERATED_USTRUCT_BODY() +public: + UPROPERTY(EditAnywhere,BlueprintReadWrite) + bool bIoStore = false; + UPROPERTY(EditAnywhere,BlueprintReadWrite) + bool bAllowBulkDataInIoStore = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TArray IoStorePakListOptions; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TArray IoStoreCommandletOptions; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TMap PlatformContainers; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bStoragePakList = true; + // Metadata/BulkDataInfo.ubulkmanifest + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bStorageBulkDataInfo = true; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPackageTracker.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPackageTracker.h new file mode 100644 index 0000000..ba56dfa --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPackageTracker.h @@ -0,0 +1,145 @@ +#pragma once +#include "FlibAssetManageHelper.h" +#include "CoreMinimal.h" +#include "UObject/UObjectArray.h" +#include "HotPatcherLog.h" + +struct FPackageTrackerBase : public FUObjectArray::FUObjectCreateListener, public FUObjectArray::FUObjectDeleteListener +{ + FPackageTrackerBase() + { + // for (TObjectIterator It; It; ++It) + // { + // UPackage* Package = *It; + // + // if (Package->GetOuter() == nullptr) + // { + // LoadedPackages.Add(Package); + // } + // } + + GUObjectArray.AddUObjectDeleteListener(this); + GUObjectArray.AddUObjectCreateListener(this); + } + + virtual ~FPackageTrackerBase() + { + GUObjectArray.RemoveUObjectDeleteListener(this); + GUObjectArray.RemoveUObjectCreateListener(this); + } + + virtual void NotifyUObjectCreated(const class UObjectBase *Object, int32 Index) override + { + if (Object->GetClass() == UPackage::StaticClass()) + { + auto Package = const_cast(static_cast(Object)); + + if (Package->GetOuter() == nullptr && !Package->GetFName().IsNone()) + { + FName AssetPathName = FName(*Package->GetPathName()); + LoadedPackages.Add(AssetPathName); + + OnPackageCreated(Package); + } + } + } + + virtual void NotifyUObjectDeleted(const class UObjectBase *Object, int32 Index) override + { + if (Object->GetClass() == UPackage::StaticClass()) + { + auto Package = const_cast(static_cast(Object)); + + if(!Package->GetFName().IsNone()) + { + + OnPackageDeleted(Package); + } + } + } + + virtual void OnUObjectArrayShutdown() + { + GUObjectArray.RemoveUObjectDeleteListener(this); + GUObjectArray.RemoveUObjectCreateListener(this); + } + virtual void OnPackageCreated(UPackage* Package){}; + virtual void OnPackageDeleted(UPackage* Package){}; + virtual const TSet& GetLoadedPackages()const{ return LoadedPackages; } + +protected: + TSet LoadedPackages; +}; + +struct FPackageTracker : public FPackageTrackerBase +{ + FPackageTracker(TSet& InExisitAssets):ExisitAssets(InExisitAssets){} + + virtual ~FPackageTracker(){} + + virtual void OnPackageCreated(UPackage* Package) override + { + FName AssetPathName = FName(*Package->GetPathName()); + LoadedPackages.Add(AssetPathName); + if(!ExisitAssets.Contains(AssetPathName)) + { + if(FPackageName::DoesPackageExist(AssetPathName.ToString())) + { + UE_LOG(LogHotPatcher,Display,TEXT("[PackageTracker] Add %s"),*AssetPathName.ToString()); + PackagesPendingSave.Add(AssetPathName); + } + else + { + UE_LOG(LogHotPatcher,Display,TEXT("[PackageTracker] %s is not valid package!"),*AssetPathName.ToString()); + } + } + } + virtual void OnPackageDeleted(UPackage* Package) override + { + FName AssetPathName = FName(*Package->GetPathName()); + if(PackagesPendingSave.Contains(AssetPathName)) + { + PackagesPendingSave.Remove(AssetPathName); + } + } + +public: + // typedef TSet PendingPackageSet; + const TSet& GetPendingPackageSet()const {return PackagesPendingSave; } +protected: + 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/BaseTypes/FPakEncryptionKeys.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPakEncryptionKeys.h new file mode 100644 index 0000000..afc2187 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPakEncryptionKeys.h @@ -0,0 +1,85 @@ +#pragma once + +// engine header +#include "CoreMinimal.h" +#include "Engine/EngineTypes.h" +#include "HAL/FileManager.h" +#include "FPakEncryptionKeys.generated.h" + +USTRUCT(BlueprintType) +struct HOTPATCHERRUNTIME_API FPakEncryptSettings +{ + GENERATED_BODY() + // Use DefaultCrypto.ini + UPROPERTY(EditAnywhere,BlueprintReadWrite) + bool bUseDefaultCryptoIni = false; + // crypto.json (option) + UPROPERTY(EditAnywhere,BlueprintReadWrite,meta=(EditCondition="!bUseDefaultCryptoIni")) + FFilePath CryptoKeys; +}; + + + +USTRUCT() +struct FEncryptionKeyEntry +{ + GENERATED_BODY() + UPROPERTY() + FString Name; + UPROPERTY() + FString Guid; + UPROPERTY() + FString Key; +}; + +USTRUCT() +struct FSignKeyItem +{ + GENERATED_BODY() + UPROPERTY() + FString Exponent; + UPROPERTY() + FString Modulus; +}; + +USTRUCT() +struct FSignKeyEntry +{ + GENERATED_BODY() + UPROPERTY() + FSignKeyItem PublicKey; + UPROPERTY() + FSignKeyItem PrivateKey; +}; + +USTRUCT() +struct FPakEncryptionKeys +{ + GENERATED_BODY(); + + UPROPERTY() + FEncryptionKeyEntry EncryptionKey; + UPROPERTY() + TArray SecondaryEncryptionKeys; + + UPROPERTY() + bool bEnablePakIndexEncryption = false; + UPROPERTY() + bool bEnablePakIniEncryption = false; + UPROPERTY() + bool bEnablePakUAssetEncryption = false; + UPROPERTY() + bool bEnablePakFullAssetEncryption = false; + UPROPERTY() + bool bDataCryptoRequired = false; + UPROPERTY() + bool PakEncryptionRequired = false; + UPROPERTY() + bool PakSigningRequired = false; + + UPROPERTY() + bool bEnablePakSigning = false; + UPROPERTY() + FSignKeyEntry SigningKey; +}; + diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPakFileInfo.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPakFileInfo.h new file mode 100644 index 0000000..266f68b --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPakFileInfo.h @@ -0,0 +1,43 @@ +#pragma once + +// project header +#include "FPakVersion.h" +// engine header +#include "CoreMinimal.h" +#include "FPakFileInfo.generated.h" + +USTRUCT(BlueprintType) +struct FPakFileInfo +{ + GENERATED_USTRUCT_BODY() +public: + + UPROPERTY(EditAnywhere,BlueprintReadWrite) + FString FileName; + UPROPERTY(EditAnywhere,BlueprintReadWrite) + FString Hash; + UPROPERTY(EditAnywhere,BlueprintReadWrite) + int32 FileSize = 0; + //UPROPERTY(EditAnywhere,BlueprintReadWrite) + //FPakVersion PakVersion; +}; + +USTRUCT(BlueprintType) +struct FPakFileArray +{ + GENERATED_USTRUCT_BODY() +public: + + UPROPERTY() + TArray PakFileInfos; +}; + +USTRUCT(BlueprintType) +struct FPakFilesMap +{ + GENERATED_USTRUCT_BODY() +public: + + UPROPERTY() + TMap PakFilesMap; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPakVersion.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPakVersion.h new file mode 100644 index 0000000..214a0d8 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPakVersion.h @@ -0,0 +1,19 @@ +#pragma once + +#include "CoreMinimal.h" +#include "FPakVersion.generated.h" + +USTRUCT(BlueprintType) +struct FPakVersion +{ + GENERATED_USTRUCT_BODY() +public: + UPROPERTY(EditAnywhere,BlueprintReadWrite) + FString VersionId; + UPROPERTY(EditAnywhere,BlueprintReadWrite) + FString BaseVersionId; + UPROPERTY(EditAnywhere,BlueprintReadWrite) + FString Date; + UPROPERTY(EditAnywhere,BlueprintReadWrite) + FString CheckCode; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPatchVersionAssetDiff.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPatchVersionAssetDiff.h new file mode 100644 index 0000000..9fe37cf --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPatchVersionAssetDiff.h @@ -0,0 +1,27 @@ +#pragma once +// project header +#include "BaseTypes/AssetManager/FAssetDependenciesInfo.h" +#include "FExternFileInfo.h" + +// engine header +#include "CoreMinimal.h" + +#include "FPlatformExternAssets.h" +#include "Engine/EngineTypes.h" +#include "FPatchVersionAssetDiff.generated.h" + +USTRUCT(BlueprintType) +struct FPatchVersionAssetDiff +{ + GENERATED_USTRUCT_BODY() +public: + FPatchVersionAssetDiff()=default; + FPatchVersionAssetDiff(const FPatchVersionAssetDiff&)=default; + + UPROPERTY(EditAnywhere) + FAssetDependenciesInfo AddAssetDependInfo; + UPROPERTY(EditAnywhere) + FAssetDependenciesInfo ModifyAssetDependInfo; + UPROPERTY(EditAnywhere) + FAssetDependenciesInfo DeleteAssetDependInfo; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPatchVersionDiff.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPatchVersionDiff.h new file mode 100644 index 0000000..9cb0864 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPatchVersionDiff.h @@ -0,0 +1,28 @@ +#pragma once +// project header +#include "FPatchVersionAssetDiff.h" +#include "FPatchVersionExternDiff.h" + +// engine header +#include "CoreMinimal.h" + +#include "Engine/EngineTypes.h" +#include "FPatchVersionDiff.generated.h" + +USTRUCT(BlueprintType) +struct FPatchVersionDiff +{ + GENERATED_USTRUCT_BODY() + FPatchVersionDiff()=default; + FPatchVersionDiff(const FPatchVersionDiff&)=default; +public: + UPROPERTY(EditAnywhere) + FPatchVersionAssetDiff AssetDiffInfo; + + // UPROPERTY(EditAnywhere) + // FPatchVersionExternDiff ExternDiffInfo; + + UPROPERTY(EditAnywhere) + TMap PlatformExternDiffInfo; + +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPatchVersionExternDiff.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPatchVersionExternDiff.h new file mode 100644 index 0000000..a680e12 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPatchVersionExternDiff.h @@ -0,0 +1,27 @@ +#pragma once +// project header +#include "BaseTypes/AssetManager/FAssetDependenciesInfo.h" +#include "FExternFileInfo.h" + +// engine header +#include "CoreMinimal.h" + +#include "FPlatformExternAssets.h" +#include "Engine/EngineTypes.h" +#include "FPatchVersionExternDiff.generated.h" + +USTRUCT(BlueprintType) +struct FPatchVersionExternDiff +{ + GENERATED_USTRUCT_BODY() + FPatchVersionExternDiff()=default; +public: + UPROPERTY(EditAnywhere) + ETargetPlatform Platform {ETargetPlatform::None}; + UPROPERTY(EditAnywhere) + TArray AddExternalFiles{}; + UPROPERTY(EditAnywhere) + TArray ModifyExternalFiles{}; + UPROPERTY(EditAnywhere) + TArray DeleteExternalFiles{}; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPatcherSpecifyAsset.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPatcherSpecifyAsset.h new file mode 100644 index 0000000..2acf831 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPatcherSpecifyAsset.h @@ -0,0 +1,32 @@ +#pragma once + +#include "FlibAssetManageHelper.h" +#include "CoreMinimal.h" +#include "Engine/EngineTypes.h" +#include "FPatcherSpecifyAsset.generated.h" + +USTRUCT(BlueprintType) +struct FPatcherSpecifyAsset +{ + GENERATED_USTRUCT_BODY() + +public: + FPatcherSpecifyAsset() + { + AssetRegistryDependencyTypes.Add(EAssetRegistryDependencyTypeEx::Packages); + } + bool operator==(const FPatcherSpecifyAsset& InAsset)const + { + bool SameAsset = (Asset == InAsset.Asset); + bool SamebAnalysisAssetDependencies = (bAnalysisAssetDependencies == InAsset.bAnalysisAssetDependencies); + bool SameAssetRegistryDependencyTypes = (AssetRegistryDependencyTypes == InAsset.AssetRegistryDependencyTypes); + + return SameAsset && SamebAnalysisAssetDependencies && SameAssetRegistryDependencyTypes; + } + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FSoftObjectPath Asset; + UPROPERTY(EditAnywhere,BlueprintReadWrite) + bool bAnalysisAssetDependencies = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite,meta=(EditCondition="bAnalysisAssetDependencies")) + TArray AssetRegistryDependencyTypes; +}; diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPlatformBasePak.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPlatformBasePak.h new file mode 100644 index 0000000..4154ada --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPlatformBasePak.h @@ -0,0 +1,21 @@ +#pragma once +// project header +#include "ETargetPlatform.h" + +// engine header +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Engine/EngineTypes.h" +#include "FPlatformBasePak.generated.h" + +USTRUCT(BlueprintType) +struct FPlatformBasePak +{ + GENERATED_BODY() + FORCEINLINE FPlatformBasePak()=default; + + UPROPERTY(EditAnywhere,BlueprintReadWrite) + ETargetPlatform Platform = ETargetPlatform::None; + UPROPERTY(EditAnywhere,BlueprintReadWrite) + TArray Paks; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPlatformExternAssets.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPlatformExternAssets.h new file mode 100644 index 0000000..fa79772 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPlatformExternAssets.h @@ -0,0 +1,33 @@ +#pragma once + +#include "ETargetPlatform.h" +#include "FExternFileInfo.h" +#include "FExternDirectoryInfo.h" + +// engine heacer +#include "CoreMinimal.h" +#include "Engine/EngineTypes.h" +#include "FPlatformExternAssets.generated.h" + + +USTRUCT(BlueprintType) +struct FPlatformExternAssets +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(EditAnywhere,BlueprintReadWrite) + ETargetPlatform TargetPlatform = ETargetPlatform::None; + + UPROPERTY(EditAnywhere,BlueprintReadWrite) + TArray AddExternFileToPak; + UPROPERTY(EditAnywhere,BlueprintReadWrite) + TArray AddExternDirectoryToPak; + + + bool operator==(const FPlatformExternAssets& R)const + { + return TargetPlatform == R.TargetPlatform; + } +}; + + diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPlatformExternFiles.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPlatformExternFiles.h new file mode 100644 index 0000000..fa6ac72 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FPlatformExternFiles.h @@ -0,0 +1,26 @@ +#pragma once +// project header +#include "AssetManager/FAssetDependenciesInfo.h" +#include "FPatcherSpecifyAsset.h" +#include "FExternFileInfo.h" +#include "ETargetPlatform.h" + +// engine header +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +#include "FPlatformExternFiles.generated.h" + +USTRUCT(BlueprintType) +struct FPlatformExternFiles +{ + GENERATED_BODY() + FORCEINLINE FPlatformExternFiles()=default; + FORCEINLINE FPlatformExternFiles(ETargetPlatform InPlatform,const TArray& InFiles): + Platform(InPlatform),ExternFiles(InFiles){} + + UPROPERTY(EditAnywhere,BlueprintReadWrite) + ETargetPlatform Platform = ETargetPlatform::None; + UPROPERTY(EditAnywhere,BlueprintReadWrite) + TArray ExternFiles; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FReplaceText.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FReplaceText.h new file mode 100644 index 0000000..c9b58f1 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FReplaceText.h @@ -0,0 +1,23 @@ +#pragma once + +#include "CoreMinimal.h" +#include "FReplaceText.generated.h" + +UENUM(BlueprintType) +enum class ESearchCaseMode :uint8 +{ + CaseSensitive, + IgnoreCase +}; +USTRUCT(BlueprintType) +struct FReplaceText +{ + GENERATED_USTRUCT_BODY() +public: + UPROPERTY(EditAnywhere,BlueprintReadWrite) + FString From; + UPROPERTY(EditAnywhere,BlueprintReadWrite) + FString To; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + ESearchCaseMode SearchCase = ESearchCaseMode::CaseSensitive; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FUnrealPakSettings.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FUnrealPakSettings.h new file mode 100644 index 0000000..c168d06 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FUnrealPakSettings.h @@ -0,0 +1,18 @@ +#pragma once + +#include "CoreMinimal.h" +#include "FUnrealPakSettings.generated.h" + +USTRUCT(BlueprintType) +struct FUnrealPakSettings +{ + GENERATED_USTRUCT_BODY() +public: + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TArray UnrealPakListOptions; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TArray UnrealCommandletOptions; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bStoragePakList = true; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FlibPakHelper.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FlibPakHelper.h new file mode 100644 index 0000000..d7e9170 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FlibPakHelper.h @@ -0,0 +1,87 @@ +// Fill out your copyright notice in the Description page of Project Settings. +#pragma once + +#include "FPakVersion.h" + +// Engine Header +#include "Resources/Version.h" +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Templates/SharedPointer.h" +#include "Dom/JsonObject.h" +#include "IPlatformFilePak.h" +#include "AssetRegistryState.h" +#include "FlibPakHelper.generated.h" + +#if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION >=26 + #define FindFilesAtPath FindPrunedFilesAtPath + #define GetFilenames GetPrunedFilenames +#endif + +UCLASS() +class HOTPATCHERRUNTIME_API UFlibPakHelper : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + + UFUNCTION(Exec) + static void ExecMountPak(FString InPakPath, int32 InPakOrder=0, FString InMountPoint=TEXT("")); + + UFUNCTION(BlueprintCallable, Category="GWorld|Flib|Pak", meta=(AdvancedDisplay="InMountPoint")) + static bool MountPak(const FString& PakPath, int32 PakOrder, const FString& InMountPoint = TEXT("")); + UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|Pak", meta = (AdvancedDisplay = "InMountPoint")) + static bool UnMountPak(const FString& PakPath); + + UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|Pak", meta=(AdvancedDisplay="InWriteFlag")) + static bool CreateFileByBytes(const FString& InFile, const TArray& InBytes, int32 InWriteFlag = 0); + + UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|Pak") + static bool ScanPlatformDirectory(const FString& InRelativePath,bool bIncludeFile,bool bIncludeDir,bool bRecursively, TArray& OutResault); + + // secrah specify extension file type file in directory + UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|Pak") + static bool ScanExtenFilesInDirectory(const FString& InRelativePath,const FString& InExtenPostfix,bool InRecursively, TArray& OutFiles); + + // search in FPaths::ProjectSavedDir()/TEXT("Extension/Versions") + UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|Pak") + static TArray ScanAllVersionDescribleFiles(); + + // Additional Pak files in ../../../PROJECT_NAME/Saved/ExtenPaks + UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|Pak") + static TArray ScanExtenPakFiles(); + + UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|Pak") + static TArray GetAllMountedPaks(); + + UFUNCTION(BlueprintCallable) + static int32 GetPakOrderByPakPath(const FString& PakFile); + + + // Default Load FApp::GetProjectName() on Enging launching + UFUNCTION(BlueprintCallable) + static bool OpenPSO(const FString& Name); + + static TArray GetPakFileList(const FString& InPak, const FString& AESKey); + static TMap GetPakEntrys(FPakFile* InPakFile); + static FSHA1 GetPakEntryHASH(FPakFile* InPakFile,const FPakEntry& PakEntry); + + static FString GetPakFileMountPoint(const FString& InPak, const FString& AESKey); + static FPakFile* GetPakFileIns(const FString& InPak, const FString& AESKey); + +public: + // reload Global&Project shaderbytecode + UFUNCTION(BlueprintCallable,Exec) + static void ReloadShaderbytecode(); + UFUNCTION(BlueprintCallable,Exec) + static bool LoadShaderbytecode(const FString& LibraryName, const FString& LibraryDir,bool bNative = false); + UFUNCTION(BlueprintCallable,Exec) + static bool LoadShaderbytecodeInDefaultDir(const FString& LibraryName); + UFUNCTION(BlueprintCallable,Exec) + static void CloseShaderbytecode(const FString& LibraryName); + + static bool LoadAssetRegistryToState(const TCHAR* Path,FAssetRegistryState& Out); + UFUNCTION(BlueprintCallable,Exec) + static bool LoadAssetRegistry(const FString& LibraryName, const FString& LibraryDir); + +}; diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FlibPakReader.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FlibPakReader.h new file mode 100644 index 0000000..ad04e2e --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/FlibPakReader.h @@ -0,0 +1,31 @@ +// // Fill out your copyright notice in the Description page of Project Settings. +// +// #pragma once +// +// #include "IPlatformFilePak.h" +// #include "CoreMinimal.h" +// +// #include "Misc/FileHelper.h" +// #include "Kismet/BlueprintFunctionLibrary.h" +// #include "FlibPakReader.generated.h" +// +// /** +// * +// */ +// UCLASS() +// class HOTPATCHERRUNTIME_API UFlibPakReader : public UBlueprintFunctionLibrary +// { +// GENERATED_BODY() +// public: +// #if PLATFORM_WINDOWS +// static TArray GetPakFileList(const FString& PakFilePath); +// static FPakFile* GetPakFileInsByPath(const FString& PakPath); +// static bool FindFileInPakFile(FPakFile* InPakFile, const FString& InFileName, FPakEntry* OutPakEntry); +// static IFileHandle* CreatePakFileHandle(IPlatformFile* InLowLevel, FPakFile* PakFile, const FPakEntry* FileEntry); +// static bool LoadFileToString(FString& Result, FArchive* InReader, const TCHAR* Filename, FFileHelper::EHashOptions VerifyFlags = FFileHelper::EHashOptions::None); +// +// +// static FArchive* CreatePakReader(FPakFile* InPakFile, IFileHandle& InHandle, const TCHAR* InFilename); +// static FString LoadPakFileToString(const FString& InPakFile,const FString& InFileName); +// #endif +// }; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/HotPatcherBaseTypes.h b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/HotPatcherBaseTypes.h new file mode 100644 index 0000000..7bd177a --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/BaseTypes/HotPatcherBaseTypes.h @@ -0,0 +1,23 @@ +#pragma once +#include "ETargetPlatform.h" + +// engine header +#include "CoreMinimal.h" +#include "Engine/EngineTypes.h" + +enum class EPatchAssetType:uint8 +{ + None, + NEW, + MODIFY +}; + +using FCookActionEvent = TFunction; +using FCookActionResultEvent = TFunction; + + +struct HOTPATCHERRUNTIME_API FCookActionCallback +{ + FCookActionEvent OnCookBegin = nullptr; + FCookActionResultEvent OnAssetCooked = nullptr; +}; diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/FExportPatchSettings.h b/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/FExportPatchSettings.h new file mode 100644 index 0000000..12d8bf5 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/FExportPatchSettings.h @@ -0,0 +1,298 @@ +// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. + +#pragma once + +// project header +#include "FUnrealPakSettings.h" +#include "FIoStoreSettings.h" +#include "FPatchVersionDiff.h" +#include "FChunkInfo.h" +#include "FReplaceText.h" +#include "ETargetPlatform.h" +#include "FExternFileInfo.h" +#include "FExternDirectoryInfo.h" +#include "FPlatformExternAssets.h" +#include "FPatcherSpecifyAsset.h" +#include "CreatePatch/HotPatcherSettingBase.h" +#include "BinariesPatchFeature.h" +#include "FPlatformBasePak.h" +#include "FPakEncryptionKeys.h" +#include "FBinariesPatchConfig.h" +#include "FHotPatcherVersion.h" +#include "FPakVersion.h" +#include "FPlatformExternAssets.h" +#include "BaseTypes/FCookShaderOptions.h" +#include "BaseTypes/FAssetRegistryOptions.h" +#include "BaseTypes.h" +// engine header +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "UObject/Object.h" +#include "Engine/EngineTypes.h" +#include "Dom/JsonObject.h" +#include "Serialization/JsonWriter.h" +#include "Serialization/JsonSerializer.h" +#include "FExportPatchSettings.generated.h" + + +USTRUCT() +struct HOTPATCHERRUNTIME_API FPatherResult +{ + GENERATED_BODY() + UPROPERTY() + TArray PatcherAssetDetails; +}; + +USTRUCT(BlueprintType) +struct FCookAdvancedOptions +{ + GENERATED_BODY() + FCookAdvancedOptions(); + // ConcurrentSave for cooking + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bCookParallelSerialize = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + int32 NumberOfAssetsPerFrame = 100; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TMap OverrideNumberOfAssetsPerFrame; + FORCEINLINE const TMap& GetOverrideNumberOfAssetsPerFrame()const{ return OverrideNumberOfAssetsPerFrame; } +}; + +/** Singleton wrapper to allow for using the setting structure in SSettingsView */ +USTRUCT(BlueprintType) +struct HOTPATCHERRUNTIME_API FExportPatchSettings:public FHotPatcherSettingBase +{ + GENERATED_USTRUCT_BODY() +public: + + FExportPatchSettings(); + virtual void Init() override; + + FORCEINLINE static FExportPatchSettings* Get() + { + static FExportPatchSettings StaticIns; + + return &StaticIns; + } + + FORCEINLINE virtual TArray& GetAddExternAssetsToPlatform()override{return AddExternAssetsToPlatform;} + + FORCEINLINE bool IsAnalysisDiffAssetDependenciesOnly()const {return bAnalysisDiffAssetDependenciesOnly;} + + FORCEINLINE FString GetVersionId()const { return VersionId; } + FString GetBaseVersion()const; + FORCEINLINE TArray GetUnrealPakListOptions()const { return GetUnrealPakSettings().UnrealPakListOptions; } + FORCEINLINE TArray GetReplacePakListTexts()const { return ReplacePakListTexts; } + FORCEINLINE TArray GetUnrealPakCommandletOptions()const { return GetUnrealPakSettings().UnrealCommandletOptions; } + FORCEINLINE TArray GetPakTargetPlatforms()const { return PakTargetPlatforms; } + TArray GetPakTargetPlatformNames()const; + + FORCEINLINE bool IsSaveDiffAnalysis()const { return IsByBaseVersion() && bStorageDiffAnalysisResults; } + FORCEINLINE TArray GetIgnoreDeletionModulesAsset()const{return IgnoreDeletionModulesAsset;} + + // FORCEINLINE bool IsPackageTracker()const { return bPackageTracker; } + FORCEINLINE bool IsIncludeAssetRegistry()const { return bIncludeAssetRegistry; } + FORCEINLINE bool IsIncludeGlobalShaderCache()const { return bIncludeGlobalShaderCache; } + FORCEINLINE bool IsIncludeShaderBytecode()const { return bIncludeShaderBytecode; } + FORCEINLINE bool IsMakeBinaryConfig()const { return bMakeBinaryConfig; } + FORCEINLINE bool IsIncludeEngineIni()const { return bIncludeEngineIni; } + FORCEINLINE bool IsIncludePluginIni()const { return bIncludePluginIni; } + FORCEINLINE bool IsIncludeProjectIni()const { return bIncludeProjectIni; } + + FORCEINLINE bool IsByBaseVersion()const { return bByBaseVersion; } + FORCEINLINE bool IsEnableExternFilesDiff()const { return bEnableExternFilesDiff; } + + FORCEINLINE bool IsIncludePakVersion()const { return bIncludePakVersionFile; } + + // chunk infomation + FORCEINLINE bool IsEnableChunk()const { return bEnableChunk; } + FORCEINLINE TArray GetChunkInfos()const { return ChunkInfos; } + + FORCEINLINE FString GetPakVersionFileMountPoint()const { return PakVersionFileMountPoint; } + static FPakVersion GetPakVersion(const FHotPatcherVersion& InHotPatcherVersion,const FString& InUtcTime); + static FString GetSavePakVersionPath(const FString& InSaveAbsPath,const FHotPatcherVersion& InVersion); + static FString GetPakCommandsSaveToPath(const FString& InSaveAbsPath, const FString& InPlatfornName, const FHotPatcherVersion& InVersion); + + FHotPatcherVersion GetNewPatchVersionInfo(); + bool GetBaseVersionInfo(FHotPatcherVersion& OutBaseVersion)const; + FString GetCurrentVersionSavePath()const; + + FORCEINLINE bool IsCustomPakNameRegular()const {return bCustomPakNameRegular;} + FORCEINLINE FString GetPakNameRegular()const { return PakNameRegular;} + FORCEINLINE bool IsCustomPakPathRegular()const {return bCustomPakPathRegular;} + FORCEINLINE FString GetPakPathRegular()const { return PakPathRegular;} + 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;} + FORCEINLINE TArray GetDefaultCommandletOptions()const {return DefaultCommandletOptions;} + + FORCEINLINE bool IsCreateDefaultChunk()const { return bCreateDefaultChunk; } + FORCEINLINE bool IsEnableMultiThread()const{ return bEnableMultiThread; } + + FORCEINLINE bool IsStorageNewRelease()const{return bStorageNewRelease;} + FORCEINLINE bool IsStoragePakFileInfo()const{return bStoragePakFileInfo;} + // FORCEINLINE bool IsBackupMetadata()const {return bBackupMetadata;} + FORCEINLINE bool IsEnableProfiling()const { return bEnableProfiling; } + + FORCEINLINE FPakEncryptSettings GetEncryptSettings()const{ return EncryptSettings; } + FORCEINLINE bool IsBinariesPatch()const{ return bBinariesPatch; } + FORCEINLINE FBinariesPatchConfig GetBinariesPatchConfig()const{ return BinariesPatchConfig; } + FORCEINLINE bool IsSharedShaderLibrary()const { return GetCookShaderOptions().bSharedShaderLibrary; } + FORCEINLINE FCookShaderOptions GetCookShaderOptions()const {return CookShaderOptions;} + FORCEINLINE FAssetRegistryOptions GetSerializeAssetRegistryOptions()const{return SerializeAssetRegistryOptions;} + FORCEINLINE bool IsImportProjectSettings()const{ return bImportProjectSettings; } + + virtual FString GetCombinedAdditionalCommandletArgs()const override; + virtual bool IsCookParallelSerialize() const { return CookAdvancedOptions.bCookParallelSerialize; } +public: + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BaseVersion") + bool bByBaseVersion = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite,Category = "BaseVersion",meta = (RelativeToGameContentDir, EditCondition="bByBaseVersion")) + FFilePath BaseVersion; + UPROPERTY(EditAnywhere, BlueprintReadWrite,Category = "PatchBaseSettings") + FString VersionId; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Asset Filters") + bool bImportProjectSettings = false; + + // require HDiffPatchUE plugin + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BinariesPatch") + bool bBinariesPatch = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BinariesPatch", meta=(EditCondition="bBinariesPatch")) + FBinariesPatchConfig BinariesPatchConfig; + + // 只对与基础包有差异的资源进行依赖分析,提高依赖分析的速度 + // UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Asset Filters",meta = (EditCondition = "!bAnalysisFilterDependencies")) + bool bAnalysisDiffAssetDependenciesOnly = false; + // allow tracking load asset when cooking + // UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Asset Filters") + bool bPackageTracker = true; + // UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cooked Files") + bool bIncludeAssetRegistry = false; + // UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cooked Files") + bool bIncludeGlobalShaderCache = false; + // UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cooked Files") + bool bIncludeShaderBytecode = false; + + // Only in UE5 + // UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ini Config Files") + bool bMakeBinaryConfig = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ini Config Files") + bool bIncludeEngineIni = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ini Config Files") + bool bIncludePluginIni = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ini Config Files") + bool bIncludeProjectIni = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "External Files") + bool bEnableExternFilesDiff = true; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "External Files") + TArray IgnoreDeletionModulesAsset; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "External Files") + TArray AddExternAssetsToPlatform; + // record patch infomation to pak + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "External Files") + bool bIncludePakVersionFile = false; + // { + // "versionId": "1.1", + // "baseVersionId": "1.0", + // "date": "2022.01.09-02.52.34", + // "checkCode": "D13EFFEB5716F00CBB823E8E8546FB610531FE37" + // } + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "External Files",meta=(EditCondition = "bIncludePakVersionFile")) + FString PakVersionFileMountPoint; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chunk Options") + bool bEnableChunk = false; + + // If the resource is not contained by any chunk, create a default chunk storage + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chunk Options", meta = (EditCondition = "bEnableChunk")) + bool bCreateDefaultChunk = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chunk Options", meta = (EditCondition = "bEnableChunk")) + TArray ChunkInfos; + + /* + * Cook Asset in current patch + * shader code gets saved inline inside material assets + * bShareMaterialShaderCode as false + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options") + bool bCookPatchAssets = true; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options", meta=(EditCondition = "bCookPatchAssets")) + FCookAdvancedOptions CookAdvancedOptions; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options", meta=(EditCondition = "bCookPatchAssets")) + FCookShaderOptions CookShaderOptions; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options", meta=(EditCondition = "bCookPatchAssets")) + FAssetRegistryOptions SerializeAssetRegistryOptions; + // support UE4.26 later + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options", meta=(EditCondition = "!bCookPatchAssets")) + FIoStoreSettings IoStoreSettings; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options") + FUnrealPakSettings UnrealPakSettings; + + // using in Pak and IO Store + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options") + TArray DefaultPakListOptions; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options") + TArray DefaultCommandletOptions; + + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options") + FPakEncryptSettings EncryptSettings; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options") + TArray ReplacePakListTexts; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options") + TArray PakTargetPlatforms; + 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")) + FString PakNameRegular = TEXT("{VERSION}_{CHUNKNAME}_{PLATFORM}_001_P"); + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options") + bool bCustomPakPathRegular = false; + // Can use value: {VERSION} {BASEVERSION} {CHUNKNAME} {PLATFORM} + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pak Options",meta=(EditCondition = "bCustomPakPathRegular")) + FString PakPathRegular = TEXT("{CHUNKNAME}/{PLATFORM}"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SaveTo") + bool bStorageNewRelease = true; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SaveTo") + bool bStoragePakFileInfo = true; + // dont display deleted asset info in patcher + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SaveTo") + bool bIgnoreDeletedAssetsInfo = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SaveTo") + bool bStorageDeletedAssetsToNewReleaseJson = true; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SaveTo",meta=(EditCondition="bByBaseVersion")) + bool bStorageDiffAnalysisResults = true; + // UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SaveTo") + // bool bStorageAssetDependencies = false; + // UPROPERTY(EditAnywhere,BlueprintReadWrite, Category = "SaveTo") + // bool bBackupMetadata = false; + + // UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Advanced") + bool bEnableMultiThread = false; + + UPROPERTY(EditAnywhere,BlueprintReadWrite, Category = "Advanced") + bool bEnableProfiling = false; + UPROPERTY(EditAnywhere,BlueprintReadWrite, Category = "Advanced") + FString StorageCookedDir = TEXT("[PROJECTDIR]/Saved/Cooked"); + + FString GetStorageCookedDir()const; + FString GetChunkSavedDir(const FString& InVersionId,const FString& InBaseVersionId,const FString& InChunkName,const FString& InPlatformName)const; + +}; + +struct HOTPATCHERRUNTIME_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; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/FExportReleaseSettings.h b/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/FExportReleaseSettings.h new file mode 100644 index 0000000..e6fe5e9 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/FExportReleaseSettings.h @@ -0,0 +1,93 @@ +// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. + +#pragma once + +// project header +#include "FPatcherSpecifyAsset.h" +#include "FExternFileInfo.h" +#include "FExternDirectoryInfo.h" +#include "FPlatformExternAssets.h" +#include "HotPatcherLog.h" +#include "CreatePatch/HotPatcherSettingBase.h" +#include "FlibPatchParserHelper.h" +#include "HotPatcherLog.h" +#include "CreatePatch/HotPatcherSettingBase.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 "FExportReleaseSettings.generated.h" + + +USTRUCT(BlueprintType) +struct HOTPATCHERRUNTIME_API FPlatformPakListFiles +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(EditAnywhere) + ETargetPlatform TargetPlatform = ETargetPlatform::None; + UPROPERTY(EditAnywhere) + TArray PakResponseFiles; + UPROPERTY(EditAnywhere) + TArray PakFiles; + UPROPERTY(EditAnywhere) + FString AESKey; +}; + +/** Singleton wrapper to allow for using the setting structur e in SSettingsView */ +USTRUCT(BlueprintType) +struct HOTPATCHERRUNTIME_API FExportReleaseSettings:public FHotPatcherSettingBase +{ + GENERATED_USTRUCT_BODY() +public: + FExportReleaseSettings(); + ~FExportReleaseSettings(); + virtual void Init() override; + virtual void ImportPakLists(); + virtual void ClearImportedPakList(); + void OnFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent); + virtual void PostEditChangeProperty(const FPropertyChangedEvent& PropertyChangedEvent); + + static FExportReleaseSettings* Get(); + FString GetVersionId()const; + + TArray GetAddExternAssetsToPlatform()const{return AddExternAssetsToPlatform;} + + FORCEINLINE bool IsBackupMetadata()const {return bBackupMetadata;} + FORCEINLINE bool IsBackupProjectConfig()const {return bBackupProjectConfig;} + FORCEINLINE bool IsByPakList()const { return ByPakList; } + FORCEINLINE TArray GetPlatformsPakListFiles()const {return PlatformsPakListFiles;} + FORCEINLINE TArray GetBackupMetadataPlatforms()const{return BackupMetadataPlatforms;} + + virtual TArray& GetAddExternAssetsToPlatform()override{ return AddExternAssetsToPlatform;} + FORCEINLINE bool IsImportProjectSettings()const{return bImportProjectSettings;} + +public: + UPROPERTY(EditAnywhere, BlueprintReadWrite,Category = "Version") + FString VersionId; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Version") + bool ByPakList = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Version", meta = (RelativeToGameContentDir, EditCondition = "ByPakList")) + TArray PlatformsPakListFiles; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Asset Filters") + bool bImportProjectSettings = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "External Files") + TArray AddExternAssetsToPlatform; + + UPROPERTY(EditAnywhere,BlueprintReadWrite, Category = "SaveTo") + bool bBackupMetadata = false; + UPROPERTY(EditAnywhere,BlueprintReadWrite, Category = "SaveTo") + bool bBackupProjectConfig = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SaveTo",meta=(EditCondition="bBackupMetadata")) + TArray BackupMetadataPlatforms; + UPROPERTY(EditAnywhere,BlueprintReadWrite,Category="Advanced") + bool bNoShaderCompile = true; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/HotPatcherContext.h b/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/HotPatcherContext.h new file mode 100644 index 0000000..2f4098b --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/HotPatcherContext.h @@ -0,0 +1,141 @@ +#pragma once + +#include "HotPatcherSettingBase.h" +#include "FPatchVersionDiff.h" +#include "FHotPatcherVersion.h" +#include "FChunkInfo.h" +#include "FPakFileInfo.h" +#include "FExportReleaseSettings.h" +#include "HotPatcherSettingBase.h" +#include "Misc/ScopedSlowTask.h" +#include "CreatePatch/ScopedSlowTaskContext.h" +#include "BaseTypes/FPackageTracker.h" + +// engine +#include "CoreMinimal.h" +#include "TimeRecorder.h" +#include "Engine/EngineTypes.h" +#include "HotPatcherContext.generated.h" + +struct FChunkInfo; +struct FHotPatcherVersion; + + +DECLARE_MULTICAST_DELEGATE_TwoParams(FExportPakProcess,const FString&,const FString&); +DECLARE_MULTICAST_DELEGATE_OneParam(FExportPakShowMsg,const FString&); + + +USTRUCT(BlueprintType) +struct HOTPATCHERRUNTIME_API FHotPatcherContext +{ + GENERATED_USTRUCT_BODY() + FHotPatcherContext()=default; + virtual ~FHotPatcherContext(){} + virtual void Init() + { + TotalTimeRecorder = MakeShareable(new TimeRecorder(GetTotalTimeRecorderName(),false)); + TotalTimeRecorder->Begin(GetTotalTimeRecorderName()); + } + virtual void Shurdown() + { + TotalTimeRecorder->End(); + } + virtual FString GetTotalTimeRecorderName()const{return TEXT("");} + //virtual FHotPatcherSettingBase* GetSettingObject() { return (FHotPatcherSettingBase*)ContextSetting; } + +public: + FExportPakProcess OnPaking; + FExportPakShowMsg OnShowMsg; + FHotPatcherSettingBase* ContextSetting; + UPROPERTY() + UScopedSlowTaskContext* UnrealPakSlowTask = nullptr; + TSharedPtr TotalTimeRecorder; +}; + +struct HOTPATCHERRUNTIME_API FPackageTrackerByDiff : public FPackageTrackerBase +{ + FPackageTrackerByDiff(struct FHotPatcherPatchContext& InContext):Context(InContext) + {} + + virtual ~FPackageTrackerByDiff(){} + + virtual void OnPackageCreated(UPackage* Package) override; + virtual void OnPackageDeleted(UPackage* Package) override{} + + const TMap& GetTrackResult()const { return TrackedAssets; } +protected: + TMap TrackedAssets; + FHotPatcherPatchContext& Context; +}; + + +USTRUCT(BlueprintType) +struct HOTPATCHERRUNTIME_API FHotPatcherPatchContext:public FHotPatcherContext +{ + GENERATED_USTRUCT_BODY() + FHotPatcherPatchContext()=default; + virtual FExportPatchSettings* GetSettingObject(){ return (FExportPatchSettings*)ContextSetting; } + virtual FString GetTotalTimeRecorderName()const{return TEXT("Generate the patch total time");} + + FPatchVersionExternDiff* GetPatcherDiffInfoByName(const FString& PlatformName); + FPlatformExternAssets* GetPatcherChunkInfoByName(const FString& PlatformName,const FString& ChunkName); + + // UPROPERTY(EditAnywhere) + class UPatcherProxy* PatchProxy; + + // base version content + UPROPERTY(EditAnywhere) + FHotPatcherVersion BaseVersion; + // current project content + UPROPERTY(EditAnywhere) + FHotPatcherVersion CurrentVersion; + + // base version and current version different + UPROPERTY(EditAnywhere) + FPatchVersionDiff VersionDiff; + + // final new version content + UPROPERTY(EditAnywhere) + FHotPatcherVersion NewReleaseVersion; + + // generated current version chunk + UPROPERTY(EditAnywhere) + FChunkInfo NewVersionChunk; + + // chunk info + UPROPERTY(EditAnywhere) + TArray PakChunks; + + UPROPERTY(EditAnywhere) + TArray AdditionalFileToPak; + + // every pak file info + // TArray PakFileProxys; + FORCEINLINE uint32 GetPakFileNum()const + { + TArray Keys; + PakFilesInfoMap.PakFilesMap.GetKeys(Keys); + return Keys.Num(); + } + FPakFilesMap PakFilesInfoMap; + + void AddExternalFile(const FString& PlatformName,const FString& ChunkName,const FExternFileInfo& AddShaderLib) + { + GetPatcherDiffInfoByName(PlatformName)->AddExternalFiles.Add(AddShaderLib); + GetPatcherChunkInfoByName(PlatformName,ChunkName)->AddExternFileToPak.Add(AddShaderLib); + } + + bool AddAsset(const FString ChunkName, const FAssetDetail& AssetDetail); +}; + +USTRUCT(BlueprintType) +struct HOTPATCHERRUNTIME_API FHotPatcherReleaseContext:public FHotPatcherContext +{ + GENERATED_USTRUCT_BODY() + FHotPatcherReleaseContext()=default; + virtual FExportReleaseSettings* GetSettingObject() { return (FExportReleaseSettings*)ContextSetting; } + virtual FString GetTotalTimeRecorderName()const{return TEXT("Generate the release total time");} + UPROPERTY(BlueprintReadOnly) + FHotPatcherVersion NewReleaseVersion; + class UReleaseProxy* ReleaseProxy; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/HotPatcherSettingBase.h b/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/HotPatcherSettingBase.h new file mode 100644 index 0000000..de60aec --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/HotPatcherSettingBase.h @@ -0,0 +1,88 @@ +#pragma once +#include "FPlatformExternFiles.h" +#include "FPatcherSpecifyAsset.h" +#include "FPlatformExternAssets.h" +#include "BaseTypes/AssetManager/FAssetDependenciesInfo.h" +// engine +#include "CoreMinimal.h" +#include "FAssetScanConfig.h" +#include "Engine/EngineTypes.h" +#include "HotPatcherSettingBase.generated.h" + +USTRUCT(BlueprintType) +struct HOTPATCHERRUNTIME_API FPatcherEntitySettingBase +{ + GENERATED_BODY(); + virtual ~FPatcherEntitySettingBase(){} +}; + + +USTRUCT(BlueprintType) +struct HOTPATCHERRUNTIME_API FHotPatcherSettingBase:public FPatcherEntitySettingBase +{ + GENERATED_USTRUCT_BODY() + FHotPatcherSettingBase(); + + virtual TArray& GetAddExternAssetsToPlatform(); + virtual void Init(); + + virtual TArray GetAllExternFilesByPlatform(ETargetPlatform InTargetPlatform,bool InGeneratedHash = false); + virtual TMap GetAllPlatfotmExternFiles(bool InGeneratedHash = false); + virtual TArray GetAddExternFilesByPlatform(ETargetPlatform InTargetPlatform); + virtual TArray GetAddExternDirectoryByPlatform(ETargetPlatform InTargetPlatform); + + virtual FString GetSaveAbsPath()const; + FORCEINLINE_DEBUGGABLE FString GetSavePath()const{ return SavePath.Path; } + + FORCEINLINE virtual bool IsStandaloneMode()const {return bStandaloneMode;} + FORCEINLINE virtual bool IsSaveConfig()const {return bStorageConfig;} + FORCEINLINE virtual TArray GetAdditionalCommandletArgs()const{return AdditionalCommandletArgs;} + virtual FString GetCombinedAdditionalCommandletArgs()const; + + FORCEINLINE virtual bool IsForceSkipContent()const{return GetAssetScanConfig().bForceSkipContent;} + FORCEINLINE virtual TArray GetForceSkipContentRules()const {return GetAssetScanConfig().ForceSkipContentRules;} + FORCEINLINE virtual TArray GetForceSkipAssets()const {return GetAssetScanConfig().ForceSkipAssets;} + virtual TArray GetAllSkipContents()const; + + FORCEINLINE virtual TArray& GetAssetIncludeFilters() { return GetAssetScanConfigRef().AssetIncludeFilters; } + FORCEINLINE virtual TArray& GetIncludeSpecifyAssets() { return GetAssetScanConfigRef().IncludeSpecifyAssets; } + FORCEINLINE virtual TArray& GetAssetIgnoreFilters() { return GetAssetScanConfigRef().AssetIgnoreFilters; } + FORCEINLINE TArray GetSpecifyAssets()const { return GetAssetScanConfig().IncludeSpecifyAssets; } + FORCEINLINE bool AddSpecifyAsset(FPatcherSpecifyAsset const& InAsset) + { + return GetAssetScanConfigRef().IncludeSpecifyAssets.AddUnique(InAsset) != INDEX_NONE; + } + FORCEINLINE virtual TArray& GetForceSkipClasses() { return GetAssetScanConfigRef().ForceSkipClasses; } + // virtual TArray GetAssetIgnoreFiltersPaths()const; + FORCEINLINE bool IsAnalysisFilterDependencies()const { return GetAssetScanConfig().bAnalysisFilterDependencies; } + FORCEINLINE bool IsRecursiveWidgetTree()const {return GetAssetScanConfig().bRecursiveWidgetTree;} + FORCEINLINE bool IsAnalysisMatInstance()const { return GetAssetScanConfig().bAnalysisMaterialInstance; } + FORCEINLINE bool IsIncludeHasRefAssetsOnly()const { return GetAssetScanConfig().bIncludeHasRefAssetsOnly; } + FORCEINLINE TArray GetAssetRegistryDependencyTypes()const { return GetAssetScanConfig().AssetRegistryDependencyTypes; } + FORCEINLINE bool IsPackageTracker()const { return GetAssetScanConfig().bPackageTracker; } + + FORCEINLINE FAssetScanConfig GetAssetScanConfig()const{ return AssetScanConfig; } + FORCEINLINE FAssetScanConfig& GetAssetScanConfigRef() { return AssetScanConfig; } + FORCEINLINE EHashCalculator GetHashCalculator()const { return HashCalculator; } + virtual ~FHotPatcherSettingBase(){} + +public: + UPROPERTY(EditAnywhere, BlueprintReadWrite,Category = "Asset Filters") + FAssetScanConfig AssetScanConfig; + + // backup current project Cooked/PLATFORM/PROJECTNAME/Metadata directory + UPROPERTY(EditAnywhere, Category = "SaveTo") + bool bStorageConfig = true; + UPROPERTY(EditAnywhere, Category = "SaveTo") + FDirectoryPath SavePath; + + UPROPERTY(EditAnywhere, Category = "Advanced") + EHashCalculator HashCalculator = EHashCalculator::MD5; + + // create a UE4Editor-cmd.exe process execute patch mission. + UPROPERTY(EditAnywhere, Category = "Advanced") + bool bStandaloneMode = true; + UPROPERTY(EditAnywhere, Category = "Advanced") + TArray AdditionalCommandletArgs; + +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/ScopedSlowTaskContext.h b/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/ScopedSlowTaskContext.h new file mode 100644 index 0000000..572e7bb --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/ScopedSlowTaskContext.h @@ -0,0 +1,47 @@ +#pragma once +#include "HotPatcherLog.h" +// engine header +#include "CoreGlobals.h" +#include "CoreMinimal.h" +#include "Misc/ScopedSlowTask.h" +#include "ScopedSlowTaskContext.generated.h" + +UCLASS() +class HOTPATCHERRUNTIME_API UScopedSlowTaskContext:public UObject +{ +public: + GENERATED_BODY() + + FORCEINLINE void init(float AmountOfWorkProgress) + { + if(!ProgressPtr.IsValid() && !IsRunningCommandlet()) + { + ProgressPtr = MakeUnique(AmountOfWorkProgress); + ProgressPtr->MakeDialog(); + } + + } + + FORCEINLINE void EnterProgressFrame(float ExpectedWorkThisFrame, const FText& Text =FText()) + { + if(ProgressPtr.IsValid() && !IsRunningCommandlet()) + { + ProgressPtr->EnterProgressFrame(ExpectedWorkThisFrame,Text); + }else + { + UE_LOG(LogHotPatcher,Display,TEXT("%s"),*Text.ToString()); + } + } + + FORCEINLINE void Final() + { + if(ProgressPtr.IsValid() && !IsRunningCommandlet()) + { + ProgressPtr->Destroy(); + ProgressPtr.Release(); + } + } + +private: + TUniquePtr ProgressPtr; +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/TimeRecorder.h b/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/TimeRecorder.h new file mode 100644 index 0000000..dcced8d --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/CreatePatch/TimeRecorder.h @@ -0,0 +1,40 @@ +#pragma once + +#include "CoreMinimal.h" +#include "HotPatcherLog.h" + +struct HOTPATCHERRUNTIME_API TimeRecorder +{ + TimeRecorder(const FString& InDisplay=TEXT(""),bool InAuto = true):Display(InDisplay),bAuto(InAuto) + { + if(bAuto) + { + Begin(InDisplay); + } + + } + ~TimeRecorder() + { + if(bAuto) + { + End(); + } + } + void Begin(const FString& InDisplay) + { + Display = InDisplay; + BeginTime = FDateTime::Now(); + } + void End() + { + CurrentTime = FDateTime::Now(); + UsedTime = CurrentTime-BeginTime; + UE_LOG(LogHotPatcher,Display,TEXT("----Time Recorder----: %s %s"),*Display,*UsedTime.ToString()); + } +public: + FDateTime BeginTime; + FDateTime CurrentTime; + FTimespan UsedTime; + FString Display; + bool bAuto = false; +}; diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/DependenciesParser/FDefaultAssetDependenciesParser.h b/HotPatcher/Source/HotPatcherRuntime/Public/DependenciesParser/FDefaultAssetDependenciesParser.h new file mode 100644 index 0000000..c1b6223 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/DependenciesParser/FDefaultAssetDependenciesParser.h @@ -0,0 +1,36 @@ +#pragma once + +#include "IAssetDependenciesParser.h" + +struct HOTPATCHERRUNTIME_API FAssetDependenciesParser : public IAssetDependenciesParser +{ + using FScanedCachesType = TMap>; + + virtual void Parse(const FAssetDependencies& InParseConfig) override; + virtual const TSet& GetrParseResults()const { return Results; }; + bool IsIgnoreAsset(const FAssetData& AssetData); + + static bool IsForceSkipAsset( + const FString& LongPackageName, + const TSet& IgnoreTypes, + const TArray& IgnoreFilters, + TArray ForceSkipFilters, + const TArray& ForceSkipPackageNames, bool bDispalyLog + ); + + TSet GatherAssetDependicesInfoRecursively( + FAssetRegistryModule& InAssetRegistryModule, + FName InLongPackageName, + const TArray& InAssetDependencyTypes, + bool bRecursively, + const TArray& IgnoreDirectories, + const TArray& ForceSkipDirectories, + const TArray& IgnorePackageNames, + const TSet& IgnoreAssetTypes, FScanedCachesType& InScanedCaches + ); +protected: + TSet Results; + FScanedCachesType ScanedCaches; + FAssetDependencies ParseConfig; +}; + diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/DependenciesParser/IAssetDependenciesParser.h b/HotPatcher/Source/HotPatcherRuntime/Public/DependenciesParser/IAssetDependenciesParser.h new file mode 100644 index 0000000..8c4646b --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/DependenciesParser/IAssetDependenciesParser.h @@ -0,0 +1,29 @@ +#pragma once + +#include "CoreMinimal.h" +#include "FlibAssetManageHelper.h" +#include "FPatcherSpecifyAsset.h" + +struct FAssetDependencies +{ + TArray IncludeFilters; + TArray IgnoreFilters; + TArray AssetRegistryDependencyTypes; + TArray InIncludeSpecifyAsset; + // like /Game/EditorOnly /Engine/VREditor + TArray ForceSkipContents; + TArray ForceSkipPackageNames; + TSet IgnoreAseetTypes; + bool bSupportWorldComposition = true; + bool bRedirector = true; + bool AnalysicFilterDependencies = true; + bool IncludeHasRefAssetsOnly = false; + +}; + +struct IAssetDependenciesParser +{ + virtual void Parse(const FAssetDependencies& ParseConfig) = 0; + // virtual const TSet& GetrParseResults()const = 0; + virtual ~IAssetDependenciesParser(){} +}; diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/FlibAssetManageHelper.h b/HotPatcher/Source/HotPatcherRuntime/Public/FlibAssetManageHelper.h new file mode 100644 index 0000000..64e4288 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/FlibAssetManageHelper.h @@ -0,0 +1,266 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "AssetManager/FAssetDependenciesInfo.h" +#include "AssetManager/FAssetDetail.h" + +// engine +#include "Engine/StreamableManager.h" + +#include "Engine/EngineTypes.h" +#include "Dom/JsonValue.h" +#include "Templates/SharedPointer.h" +#include "AssetRegistryModule.h" +#include "CoreMinimal.h" +#include "Templates/SharedPointer.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "FlibAssetManageHelper.generated.h" + +#define JSON_MODULE_LIST_SECTION_NAME TEXT("ModuleList") +#define JSON_ALL_INVALID_ASSET_SECTION_NAME TEXT("InValidAsset") +#define JSON_ALL_ASSETS_LIST_SECTION_NAME TEXT("AssetsList") +#define JSON_ALL_ASSETS_Detail_SECTION_NAME TEXT("AssetsDetail") + +USTRUCT(BlueprintType) +struct FPackageInfo +{ + GENERATED_USTRUCT_BODY() +public: + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FString AssetName; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FString AssetGuid; +}; + +UENUM(BlueprintType) +enum class EAssetRegistryDependencyTypeEx :uint8 +{ + None = 0x00, + // Dependencies which don't need to be loaded for the object to be used (i.e. soft object paths) + Soft = 0x01, + + // Dependencies which are required for correct usage of the source asset, and must be loaded at the same time + Hard = 0x02, + + // References to specific SearchableNames inside a package + SearchableName = 0x04, + + // Indirect management references, these are set through recursion for Primary Assets that manage packages or other primary assets + SoftManage = 0x08, + + // Reference that says one object directly manages another object, set when Primary Assets manage things explicitly + HardManage = 0x10, + + Packages = Soft | Hard, + + Manage = SoftManage | HardManage, + + All = Soft | Hard | SearchableName | SoftManage | HardManage +}; + +UENUM(BlueprintType) +enum class EHotPatcherMatchModEx :uint8 +{ + StartWith, + Equal +}; +UCLASS() +class HOTPATCHERRUNTIME_API UFlibAssetManageHelper : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() +public: + + UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|AssetManagerEx") + static FString PackagePathToFilename(const FString& InPackagePath); + UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|AssetManagerEx") + static FString LongPackageNameToFilename(const FString& InLongPackageName); + + UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|AssetManagerEx") + static bool FilenameToPackagePath(const FString& InAbsPath,FString& OutPackagePath); + + + static void UpdateAssetMangerDatabase(bool bForceRefresh); + // - AssetPath : /Game/BP/BP_Actor.BP_Actor + // - LongPackageName : /Game/BP/BP_Actor + // - AssetName : BP_Actor + + UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|AssetManagerEx") + static FString LongPackageNameToPackagePath(const FString& InLongPackageName); + static FString PackagePathToLongPackageName(const FString& PackagePath); + + 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 + UFUNCTION(BlueprintPure, Category = "GWorld|Flib|AssetManager", meta = (CommutativeAssociativeBinaryOperator = "true")) + static FAssetDependenciesInfo CombineAssetDependencies(const FAssetDependenciesInfo& A, const FAssetDependenciesInfo& B); + + // Get All invalid reference asset + static void GetAllInValidAssetInProject(FAssetDependenciesInfo InAllDependencies, TArray &OutInValidAsset, TArray InIgnoreModules = {}); + + + static bool GetAssetReferenceByLongPackageName(const FString& LongPackageName,const TArray& SearchAssetDepTypes, TArray& OutRefAsset); + static bool GetAssetReference(const FAssetDetail& InAsset,const TArray& SearchAssetDepTypes, TArray& OutRefAsset); + static void GetAssetReferenceRecursively(const FAssetDetail& InAsset, + const TArray& SearchAssetDepTypes, + const TArray& SearchAssetsTypes, + TArray& OutRefAsset, bool bRecursive = true); + UFUNCTION(BlueprintPure, BlueprintCallable, Category = "GWorld|Flib|AssetManager") + static bool GetAssetReferenceEx(const FAssetDetail& InAsset, const TArray& SearchAssetDepTypes, TArray& OutRefAsset); + + + static FAssetDetail GetAssetDetailByPackageName(const FString& InPackageName); + + + UFUNCTION(BlueprintPure, BlueprintCallable, Category = "GWorld|Flib|AssetManager") + static bool GetRedirectorList(const TArray& InFilterPackagePaths, TArray& OutRedirector); + static bool GetSpecifyAssetData(const FString& InLongPackageName, TArray& OutAssetData,bool InIncludeOnlyOnDiskAssets); + // /Game all uasset/umap files + static TArray GetAssetsByFilter(const FString& InFilter); + static bool GetAssetsData(const TArray& InFilterPaths, TArray& OutAssetData, bool bLocalIncludeOnlyOnDiskAssets = true); + static bool GetAssetsDataByDisk(const TArray& InFilterPaths, TArray& OutAssetData); + static bool GetSingleAssetsData(const FString& InPackagePath, FAssetData& OutAssetData); + static bool GetAssetsDataByPackageName(const FString& InPackageName, FAssetData& OutAssetData); + static bool GetClassStringFromFAssetData(const FAssetData& InAssetData,FString& OutAssetType); + static bool ConvFAssetDataToFAssetDetail(const FAssetData& InAssetData,FAssetDetail& OutAssetDetail); + UFUNCTION(BlueprintPure, BlueprintCallable, Category = "GWorld|Flib|AssetManager") + static bool GetSpecifyAssetDetail(const FString& InLongPackageName, FAssetDetail& OutAssetDetail); + + // 过滤掉没有引用的资源 + UFUNCTION(BlueprintPure, BlueprintCallable, Category = "GWorld|Flib|AssetManager") + static void FilterNoRefAssets(const TArray& InAssetsDetail, TArray& OutHasRefAssetsDetail, TArray& OutDontHasRefAssetsDetail); + + UFUNCTION(BlueprintPure, BlueprintCallable, Category = "GWorld|Flib|AssetManager") + static void FilterNoRefAssetsWithIgnoreFilter(const TArray& InAssetsDetail,const TArray& InIgnoreFilters, TArray& OutHasRefAssetsDetail, TArray& OutDontHasRefAssetsDetail); + + UFUNCTION(BlueprintPure, BlueprintCallable, Category = "GWorld|Flib|AssetManager") + static bool CombineAssetsDetailAsFAssetDepenInfo(const TArray& InAssetsDetailList,FAssetDependenciesInfo& OutAssetInfo); + + UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|AssetManager") + static bool ConvLongPackageNameToCookedPath( + const FString& InProjectAbsDir, + const FString& InPlatformName, + const FString& InLongPackageName, + TArray& OutCookedAssetPath, + TArray& OutCookedAssetRelativePath, + const FString& OverrideCookedDir + ); + // UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|AssetManager") + static bool MakePakCommandFromAssetDependencies( + const FString& InProjectDir, + const FString& OverrideCookedDir, + const FString& InPlatformName, + const FAssetDependenciesInfo& InAssetDependencies, + //const TArray &InCookParams, + TArray& OutCookCommand, + TFunction&,const TArray&,const FString&,const FString&)> InReceivePakCommand = [](const TArray&,const TArray&, const FString&, const FString&) {}, + TFunction IsIoStoreAsset = [](const FString&)->bool{return false;} + ); + // UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|AssetManager") + static bool MakePakCommandFromLongPackageName( + const FString& InProjectDir, + const FString& OverrideCookedDir, + const FString& InPlatformName, + const FString& InAssetLongPackageName, + //const TArray &InCookParams, + TArray& OutCookCommand, + TFunction&, const TArray&,const FString&, const FString&)> InReceivePakCommand = [](const TArray&,const TArray&, const FString&, const FString&) {}, + TFunction IsIoStoreAsset = [](const FString&)->bool{return false;} + ); + // UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|AssetManager") + static bool CombineCookedAssetCommand( + const TArray &InAbsPath, + const TArray& InRelativePath, + //const TArray& InParams, + TArray& OutPakCommand, + TArray& OutIoStoreCommand, + TFunction IsIoStoreAsset = [](const FString&)->bool{return false;} + ); + UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|AssetManager") + static bool ExportCookPakCommandToFile(const TArray& InCommand, const FString& InFile); + + UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|AssetManagerEx") + static bool SaveStringToFile(const FString& InFile, const FString& InString); + UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|AssetManagerEx") + static bool LoadFileToString(const FString& InFile, FString& OutString); + + + static FString GetAssetBelongModuleName(const FString& InAssetRelativePath); + + // conv /Game/AAAA/ to /D:/PROJECTNAME/Content/AAAA/ + UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|AssetManagerEx") + static bool ConvRelativeDirToAbsDir(const FString& InRelativePath, FString& OutAbsPath); + UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|AssetManagerEx") + static void GetAllEnabledModuleName(TMap& OutModules); + UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|AssetManagerEx") + static bool GetModuleNameByRelativePath(const FString& InRelativePath, FString& OutModuleName); + UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|AssetManagerEx") + static bool ModuleIsEnabled(const FString& InModuleName); + UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|AssetManagerEx") + static bool GetEnableModuleAbsDir(const FString& InModuleName, FString& OutPath); + UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|AssetManagerEx") + static bool GetPluginModuleAbsDir(const FString& InPluginModuleName, FString& OutPath); + + UFUNCTION(BlueprintCallable, Category = "GWorld|Flib|AssetManagerEx") + static bool FindFilesRecursive(const FString& InStartDir, TArray& OutFileList, bool InRecursive = true); + + static uint32 ParserAssetDependenciesInfoNumber(const FAssetDependenciesInfo& AssetDependenciesInfo, TMap); + static FString ParserModuleAssetsNumMap(const TMap& InMap); + + static EAssetRegistryDependencyType::Type ConvAssetRegistryDependencyToInternal(const EAssetRegistryDependencyTypeEx& InType); + + static void GetAssetDataInPaths(const TArray& Paths, TArray& OutAssetData); + + static void ExcludeContentForAssetDependenciesDetail(FAssetDependenciesInfo& AssetDependencies,const TArray& ExcludeRules = {TEXT("")},EHotPatcherMatchModEx MatchMod = EHotPatcherMatchModEx::StartWith); + + static TArray DirectoriesToStrings(const TArray& DirectoryPaths); + static TArray SoftObjectPathsToStrings(const TArray& SoftObjectPaths); + static TSet GetClassesNames(const TArray CLasses); + + static FString NormalizeContentDir(const FString& Dir); + static TArray NormalizeContentDirs(const TArray& Dirs); + + static FStreamableManager& GetStreamableManager(); + + // Default priority for all async loads + static const uint32 DefaultAsyncLoadPriority = 0; + // Priority to try and load immediately + static const uint32 AsyncLoadHighPriority = 100; + static TSharedPtr LoadObjectAsync(FSoftObjectPath ObjectPath,TFunction Callback,uint32 Priority); + static void LoadPackageAsync(FSoftObjectPath ObjectPath,TFunction Callback = nullptr,uint32 Priority = DefaultAsyncLoadPriority); + static UPackage* LoadPackage( UPackage* InOuter, const TCHAR* InLongPackageName, uint32 LoadFlags, FArchive* InReaderOverride = nullptr); + static UPackage* GetPackage(FName PackageName); + + // static TArray GetPackagesByClass(TArray& Packages, UClass* Class, bool RemoveFromSrc); + + static TArray LoadPackagesForCooking(const TArray& SoftObjectPaths, bool bStorageConcurrent); + + static bool MatchIgnoreTypes(const FString& LongPackageName, TSet IgnoreTypes, FString& MatchTypeStr); + static bool MatchIgnoreFilters(const FString& LongPackageName, const TArray& IgnoreDirs, FString& MatchDir); + + static bool ContainsRedirector(const FName& PackageName, TMap& RedirectedPaths); + + static TArray FindClassObjectInPackage(UPackage* Package,UClass* FindClass); + static bool HasClassObjInPackage(UPackage* Package,UClass* FindClass); + static TArray GetAssetDetailsByClass(TArray& AllAssetDetails,UClass* Class,bool RemoveFromSrc); + static TArray GetAssetPathsByClass(TArray& AllAssetDetails,UClass* Class,bool RemoveFromSrc); + + static bool IsRedirector(const FAssetDetail& Src,FAssetDetail& Out); + static void ReplaceReditector(TArray& SrcAssets); + static void RemoveInvalidAssets(TArray& SrcAssets); + + static FName GetAssetDataClasses(const FAssetData& Data); + static FName GetObjectPathByAssetData(const FAssetData& Data); + static bool bIncludeOnlyOnDiskAssets; + + static void UpdateAssetRegistryData(const FString& PackageName); + static TArray GetPackgeFiles(const FString& LongPackageName,const FString& Extension); +}; + + diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/FlibPatchParserHelper.h b/HotPatcher/Source/HotPatcherRuntime/Public/FlibPatchParserHelper.h new file mode 100644 index 0000000..5f19f59 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/FlibPatchParserHelper.h @@ -0,0 +1,252 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +//project header +#include "BaseTypes/FAssetScanConfig.h" +#include "FChunkInfo.h" +#include "FPakFileInfo.h" +#include "FReplaceText.h" +#include "AssetManager/FAssetDependenciesInfo.h" +#include "FHotPatcherVersion.h" +#include "FPatchVersionDiff.h" +#include "FlibPakHelper.h" +#include "FExternDirectoryInfo.h" +#include "FExternDirectoryInfo.h" +#include "FHotPatcherAssetDependency.h" +#include "FCookerConfig.h" +#include "FPlatformExternFiles.h" +#include "Templates/HotPatcherTemplateHelper.hpp" + +// engine header +#include "CoreMinimal.h" +#include "Resources/Version.h" +#include "JsonObjectConverter.h" +#include "Misc/CommandLine.h" +#include "FPlatformExternAssets.h" +#include "AssetRegistryState.h" +#include "Containers/UnrealString.h" +#include "CreatePatch/FExportPatchSettings.h" +#include "Templates/SharedPointer.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Misc/FileHelper.h" +#include "FlibPatchParserHelper.generated.h" + +struct FExportPatchSettings; +/** + * + */ +UCLASS() +class HOTPATCHERRUNTIME_API UFlibPatchParserHelper : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() +public: + + UFUNCTION(BlueprintCallable, Category = "HotPatcher|Flib") + static TArray GetAvailableMaps(FString GameName, bool IncludeEngineMaps,bool IncludePluginMaps, bool Sorted); + UFUNCTION(BlueprintCallable, Category = "HotPatcher|Flib") + static FString GetProjectName(); + UFUNCTION(BlueprintCallable) + static FString GetProjectFilePath(); + + static FHotPatcherVersion ExportReleaseVersionInfoByChunk( + const FString& InVersionId, + const FString& InBaseVersion, + const FString& InDate, + const FChunkInfo& InChunkInfo, + bool InIncludeHasRefAssetsOnly = false, + bool bInAnalysisFilterDependencies = true, EHashCalculator HashCalculator = EHashCalculator::NoHash + ); + static void RunAssetScanner(FAssetScanConfig ScanConfig,FHotPatcherVersion& ExportVersion); + static void ExportExternAssetsToPlatform(const TArray& AddExternAssetsToPlatform, FHotPatcherVersion& ExportVersion, bool bGenerateHASH, EHashCalculator + HashCalculator); + + UFUNCTION(BlueprintCallable, Category = "HotPatcher|Flib") + static bool DiffVersionAssets(const FAssetDependenciesInfo& InNewVersion, + const FAssetDependenciesInfo& InBaseVersion, + FAssetDependenciesInfo& OutAddAsset, + FAssetDependenciesInfo& OutModifyAsset, + FAssetDependenciesInfo& OutDeleteAsset + ); + + // UFUNCTION() + static bool DiffVersionAllPlatformExFiles( + const FExportPatchSettings& PatchSetting, + const FHotPatcherVersion& InBaseVersion, + const FHotPatcherVersion& InNewVersion, + TMap& OutDiff + ); + UFUNCTION() + static FPlatformExternFiles GetAllExFilesByPlatform(const FPlatformExternAssets& InPlatformConf, bool InGeneratedHash=true, EHashCalculator HashCalculator = EHashCalculator::NoHash); + UFUNCTION(BlueprintCallable, Category = "HotPatcher|Flib") + static bool GetPakFileInfo(const FString& InFile,FPakFileInfo& OutFileInfo); + + // Cooked/PLATFORM_NAME/Engine/GlobalShaderCache-*.bin + UFUNCTION(BlueprintCallable, Category = "HotPatcher|Flib") + static TArray GetCookedGlobalShaderCacheFiles(const FString& InProjectDir,const FString& InPlatformName); + // Cooked/PLATFORN_NAME/PROJECT_NAME/AssetRegistry.bin + UFUNCTION(BlueprintCallable, Category = "HotPatcher|Flib") + static bool GetCookedAssetRegistryFiles(const FString& InProjectAbsDir, const FString& InProjectName, const FString& InPlatformName,FString& OutFiles); + // Cooked/PLATFORN_NAME/PROJECT_NAME/Content/ShaderArchive-*.ushaderbytecode + UFUNCTION(BlueprintCallable, Category = "HotPatcher|Flib") + static bool GetCookedShaderBytecodeFiles(const FString& InProjectAbsDir, const FString& InProjectName, const FString& InPlatformName,bool InGalobalBytecode,bool InProjectBytecode, TArray& OutFiles); + + // UFUNCTION(BlueprintCallable, Category = "HotPatcher|Flib") + static bool ConvIniFilesToPakCommands( + const FString& InEngineAbsDir, + const FString& InProjectAbsDir, + const FString& InProjectName, + // const TArray& InPakOptions, + const TArray& InIniFiles, + TArray& OutCommands, + TFunction InReceiveCommand = [](const FPakCommand&) {}); + + // UFUNCTION(BlueprintCallable, Category = "HotPatcher|Flib") + static bool ConvNotAssetFileToPakCommand( + const FString& InProjectDir, + const FString& InPlatformName, + // const TArray& InPakOptions, + const FString& InCookedFile, + FString& OutCommand, + TFunction InReceiveCommand = [](const FPakCommand&) {}); + // static bool ConvNotAssetFileToExFile(const FString& InProjectDir, const FString& InPlatformName, const FString& InCookedFile, FExternFileInfo& OutExFile); + UFUNCTION(BlueprintCallable, Category = "HotPatcher|Flib") + static FString HashStringWithSHA1(const FString &InString); + + UFUNCTION(BlueprintCallable, Category = "HotPatcher|Flib") + static TArray GetIniConfigs(const FString& InSearchDir, const FString& InPlatformName); + // return abslute path + UFUNCTION(BlueprintCallable, Category = "HotPatcher|Flib") + static TArray GetProjectIniFiles(const FString& InProjectDir,const FString& InPlatformName); + + UFUNCTION(BlueprintCallable, Category = "HotPatcher|Flib") + static TArray GetEngineConfigs(const FString& InPlatformName); + + UFUNCTION(BlueprintCallable, Category = "HotPatcher|Flib") + static TArray GetEnabledPluginConfigs(const FString& InPlatformName); + + + static TArray ParserExDirectoryAsExFiles(const TArray& InExternDirectorys); + static TArray ParserExFilesInfoAsAssetDetailInfo(const TArray& InExFiles); + + // get Engine / Project / Plugin ini files + static TArray GetIniFilesByPakInternalInfo(const FPakInternalInfo& InPakInternalInfo,const FString& PlatformName); + // get AssetRegistry.bin / GlobalShaderCache / ShaderBytecode + static TArray GetCookedFilesByPakInternalInfo( + const FPakInternalInfo& InPakInternalInfo, + const FString& PlatformName); + + // static TArray GetInternalFilesAsExFiles(const FPakInternalInfo& InPakInternalInfo, const FString& InPlatformName); + static TArray GetPakCommandsFromInternalInfo( + const FPakInternalInfo& InPakInternalInfo, + const FString& PlatformName, + // const TArray& InPakOptions, + TFunction InReceiveCommand=[](const FPakCommand&) {}); + + static FChunkInfo CombineChunkInfo(const FChunkInfo& R, const FChunkInfo& L); + static FChunkInfo CombineChunkInfos(const TArray& Chunks); + + static TArray GetDirectoryPaths(const TArray& InDirectoryPath); + + // static TArray GetExternFilesFromChunk(const FChunkInfo& InChunk, TArray InTargetPlatforms, bool bCalcHash = false); + TMap GetAllPlatformExternFilesFromChunk(const FChunkInfo& InChunk, bool bCalcHash); + + static FChunkAssetDescribe CollectFChunkAssetsDescribeByChunk( + const FHotPatcherSettingBase* PatcheSettings, + const FPatchVersionDiff& DiffInfo, const FChunkInfo& Chunk, TArray Platforms + ); + + static TArray CollectPakCommandsStringsByChunk( + const FPatchVersionDiff& DiffInfo, + const FChunkInfo& Chunk, + const FString& PlatformName, + // const TArray& PakOptions, + const FExportPatchSettings* PatcheSettings = nullptr + ); + + static TArray CollectPakCommandByChunk( + const FPatchVersionDiff& DiffInfo, + const FChunkInfo& Chunk, + const FString& PlatformName, + // const TArray& PakOptions, + const FExportPatchSettings* PatcheSettings=nullptr + ); + + static TArray GetPakCommandStrByCommands(const TArray& PakCommands, const TArray& InReplaceTexts = TArray{},bool bIoStore=false); + static bool GetCookProcCommandParams(const FCookerConfig& InConfig,FString& OutParams); + static void ExcludeContentForVersionDiff(FPatchVersionDiff& VersionDiff,const TArray& ExcludeRules = {TEXT("")},EHotPatcherMatchModEx matchMod=EHotPatcherMatchModEx::StartWith); + static FString MountPathToRelativePath(const FString& InMountPath); + + + static TMap GetReplacePathMarkMap(); + static FString ReplaceMark(const FString& Src); + static FString ReplaceMarkPath(const FString& Src); + // [PORJECTDIR] to real path + static void ReplacePatherSettingProjectDir(TArray& PlatformAssets); + + + static TArray GetUnCookUassetExtensions(); + static TArray GetCookedUassetExtensions(); + static bool IsCookedUassetExtensions(const FString& InAsset); + static bool IsUnCookUassetExtension(const FString& InAsset); + + // ../../../Content/xxxx.uasset to D:/xxxx/xxx/xxx.uasset + static FString AssetMountPathToAbs(const FString& InAssetMountPath); + // ../../../Content/xxxx.uasset to /Game/xxxx + static FString UAssetMountPathToPackagePath(const FString& InAssetMountPath); + + static bool MatchStrInArray(const FString& InStr,const TArray& InArray); + static FString LoadAESKeyStringFromCryptoFile(const FString& InCryptoJson); + static FAES::FAESKey LoadAESKeyFromCryptoFile(const FString& InCryptoJson); +public: + static bool GetPluginPakPathByName(const FString& PluginName,FString& uPluginAbsPath,FString& uPluginMountPath); + // ../../../Example/Plugin/XXXX/ + static FString GetPluginMountPoint(const FString& PluginName); + // [PRIJECTDIR]/AssetRegistry to ../../../Example/AssetRegistry + static FString ParserMountPointRegular(const FString& Src); + +public: + UFUNCTION(BlueprintCallable) + static void ReloadShaderbytecode(); + UFUNCTION(BlueprintCallable,Exec) + static bool LoadShaderbytecode(const FString& LibraryName, const FString& LibraryDir); + UFUNCTION(BlueprintCallable,Exec) + static void CloseShaderbytecode(const FString& LibraryName); + +public: + // Encrypt + static FPakEncryptionKeys GetCryptoByProjectSettings(); + static FEncryptSetting GetCryptoSettingsByJson(const FString& CryptoJson); + + static FEncryptSetting GetCryptoSettingByPakEncryptSettings(const FPakEncryptSettings& Config); + + static bool SerializePakEncryptionKeyToFile(const FPakEncryptionKeys& PakEncryptionKeys,const FString& ToFile); + + static TArray GetDefaultForceSkipContentDir(); + + static FSHAHash FileSHA1Hash(const FString& Filename); + + static FString FileHash(const FString& Filename,EHashCalculator Calculator); + + template + static bool SerializeStruct(T SerializeStruct,const FString& SaveTo) + { + SCOPED_NAMED_EVENT_TEXT("SerializeStruct",FColor::Red); + FString SaveToPath = UFlibPatchParserHelper::ReplaceMark(SaveTo); + FString SerializedJsonContent; + THotPatcherTemplateHelper::TSerializeStructAsJsonString(SerializeStruct,SerializedJsonContent); + return FFileHelper::SaveStringToFile(SerializedJsonContent,*FPaths::ConvertRelativePathToFull(SaveToPath)); + } + + 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 GetTargetPlatformsStr(const TArray& Platforms); + static FString MergeOptionsAsCmdline(const TArray& InOptions); + static FString GetPlatformsStr(TArray Platforms); + + static bool GetCmdletBoolValue(const FString& Token,bool& OutValue); + + static FString ReplacePakRegular(const FReplacePakRegular& RegularConf, const FString& InRegular); +}; + diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/FlibShaderPipelineCacheHelper.h b/HotPatcher/Source/HotPatcherRuntime/Public/FlibShaderPipelineCacheHelper.h new file mode 100644 index 0000000..ddcd207 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/FlibShaderPipelineCacheHelper.h @@ -0,0 +1,50 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "FlibShaderPipelineCacheHelper.generated.h" + + +UENUM(BlueprintType) +enum class EPSOSaveMode : uint8 +{ + Incremental = 0, // Fast(er) approach which saves new entries incrementally at the end of the file, replacing the table-of-contents, but leaves everything else alone. + BoundPSOsOnly = 1, // Slower approach which consolidates and saves all PSOs used in this run of the program, removing any entry that wasn't seen, and sorted by the desired sort-mode. + SortedBoundPSOs = 2 // Slow save consolidates all PSOs used on this device that were never part of a cache file delivered in game-content, sorts entries into the desired order and will thus read-back from disk. +}; + +/** + * + */ +UCLASS() +class HOTPATCHERRUNTIME_API UFlibShaderPipelineCacheHelper : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() +public: + UFUNCTION(BlueprintCallable) + static bool LoadShaderPipelineCache(const FString& Name); + + UFUNCTION(BlueprintCallable) + static bool EnableShaderPipelineCache(bool bEnable); + + UFUNCTION(BlueprintCallable) + static bool SavePipelineFileCache(EPSOSaveMode Mode); + + // r.ShaderPipelineCache.LogPSO + // 1 Logs new PSO entries into the file cache and allows saving. + UFUNCTION(BlueprintCallable) + static bool EnableLogPSO(bool bEnable); + + UFUNCTION(BlueprintCallable) + static bool EnableSaveBoundPSOLog(bool bEnable); + + UFUNCTION(BlueprintCallable) + static bool IsEnabledUsePSO(); + UFUNCTION(BlueprintCallable) + static bool IsEnabledLogPSO(); + UFUNCTION(BlueprintCallable) + static bool IsEnabledSaveBoundPSOLog(); + +}; diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/HotPatcherAssetManager.h b/HotPatcher/Source/HotPatcherRuntime/Public/HotPatcherAssetManager.h new file mode 100644 index 0000000..fae0fbb --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/HotPatcherAssetManager.h @@ -0,0 +1,18 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/AssetManager.h" +#include "HotPatcherAssetManager.generated.h" + +/** + * + */ +UCLASS() +class HOTPATCHERRUNTIME_API UHotPatcherAssetManager : public UAssetManager +{ + GENERATED_BODY() +public: + virtual void ScanPrimaryAssetTypesFromConfig() override; +}; diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/HotPatcherLog.h b/HotPatcher/Source/HotPatcherRuntime/Public/HotPatcherLog.h new file mode 100644 index 0000000..688730f --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/HotPatcherLog.h @@ -0,0 +1,6 @@ +#pragma once +// engine header +#include "CoreMinimal.h" +#include "Logging/LogMacros.h" + +HOTPATCHERRUNTIME_API DECLARE_LOG_CATEGORY_EXTERN(LogHotPatcher, Log, All); diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/HotPatcherRuntime.h b/HotPatcher/Source/HotPatcherRuntime/Public/HotPatcherRuntime.h new file mode 100644 index 0000000..b478cb5 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/HotPatcherRuntime.h @@ -0,0 +1,17 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +extern HOTPATCHERRUNTIME_API bool GForceSingleThread; + +class FHotPatcherRuntimeModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/MountListener.h b/HotPatcher/Source/HotPatcherRuntime/Public/MountListener.h new file mode 100644 index 0000000..f09b3e9 --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/MountListener.h @@ -0,0 +1,48 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" +#include "GenericPlatform/GenericPlatformFile.h" +#include "MountListener.generated.h" + + +USTRUCT(Blueprintable,BlueprintType) +struct HOTPATCHERRUNTIME_API FPakMountInfo +{ + GENERATED_USTRUCT_BODY() + UPROPERTY(EditAnywhere,BlueprintReadWrite) + FString Pak; + UPROPERTY(EditAnywhere,BlueprintReadWrite) + int32 PakOrder = 0; +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnListenerMountPak,FPakMountInfo,PakInfo); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnListenerUnMountPak,const FString&,PakName); +/** +* +*/ +UCLASS(Blueprintable,BlueprintType) +class HOTPATCHERRUNTIME_API UMountListener : public UObject +{ + GENERATED_UCLASS_BODY() +public: + + UFUNCTION(BlueprintCallable) + void Init(); + + void OnMountPak(const TCHAR* PakFileName, int32 ChunkID = 0); + + virtual bool OnUnMountPak(const FString& Pak); + + virtual TMap& GetMountedPaks(); + + UPROPERTY(BlueprintAssignable) + FOnListenerMountPak OnMountPakDelegate; + UPROPERTY(BlueprintAssignable) + FOnListenerUnMountPak OnUnMountPakDelegate; + + private: + TMap PaksMap; +}; diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/TargetPlatformRegister.h b/HotPatcher/Source/HotPatcherRuntime/Public/TargetPlatformRegister.h new file mode 100644 index 0000000..06aed0e --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/TargetPlatformRegister.h @@ -0,0 +1,9 @@ +#pragma once +#include "CoreMinimal.h" +#include "TargetPlatformRegister.generated.h" + +UCLASS() +class HOTPATCHERRUNTIME_API UTargetPlatformRegister:public UObject +{ + GENERATED_UCLASS_BODY() +}; \ No newline at end of file diff --git a/HotPatcher/Source/HotPatcherRuntime/Public/Templates/HotPatcherTemplateHelper.hpp b/HotPatcher/Source/HotPatcherRuntime/Public/Templates/HotPatcherTemplateHelper.hpp new file mode 100644 index 0000000..6c5feaa --- /dev/null +++ b/HotPatcher/Source/HotPatcherRuntime/Public/Templates/HotPatcherTemplateHelper.hpp @@ -0,0 +1,362 @@ +#pragma once + +#include "CoreMinimal.h" +#include "JsonObjectConverter.h" +#include "HAL/PlatformMisc.h" +#include "Misc/EnumRange.h" +#include "Resources/Version.h" + +// cpp standard +#include +#include +#include +#include +#include +#include "Resources/Version.h" + +#if ENGINE_MAJOR_VERSION <=4 && ENGINE_MINOR_VERSION < 25 +#define FProperty UProperty +#endif + + +#define DECAL_GETCPPTYPENAME_SPECIAL(TypeName) \ +namespace THotPatcherTemplateHelper \ +{\ + template<>\ + std::string GetCPPTypeName()\ + {\ + return std::string(#TypeName);\ + };\ +}; + + +namespace THotPatcherTemplateHelper +{ + FORCEINLINE std::vector split(const std::string& s, char seperator) + { + std::vector output; + std::string::size_type prev_pos = 0, pos = 0; + while((pos = s.find(seperator, pos)) != std::string::npos) + { + std::string substring( s.substr(prev_pos, pos-prev_pos) ); + output.push_back(substring); + prev_pos = ++pos; + } + output.push_back(s.substr(prev_pos, pos-prev_pos)); // Last word + return output; + } + + template + static std::string GetCPPTypeName() + { + // std::string type_name = typeid(T).name(); + // std::vector split_results = THotPatcherTemplateHelper::split(type_name,' '); + // std::string result = split_results.at(split_results.size()-1); + std::string result; + // std::for_each(type_name.begin(),type_name.end(),[&result](const char& character){if(!std::isdigit(character)) result.push_back(character);}); + return result; + } + + template + static UEnum* GetUEnum() + { + SCOPED_NAMED_EVENT_TEXT("GetUEnum",FColor::Red); +#if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 21 + UEnum* FoundEnum = StaticEnum(); +#else + FString EnumTypeName = ANSI_TO_TCHAR(THotPatcherTemplateHelper::GetCPPTypeName().c_str()); + UEnum* FoundEnum = FindObject(ANY_PACKAGE, *EnumTypeName, true); +#endif + return FoundEnum; + } + + template + FString GetEnumNameByValue(ENUM_TYPE InEnumValue, bool bFullName = false) + { + SCOPED_NAMED_EVENT_TEXT("GetEnumNameByValue",FColor::Red); + FString result; + { + FString TypeName; + FString ValueName; + UEnum* FoundEnum = nullptr; + +#if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 21 + FoundEnum = StaticEnum(); +#else + FString EnumTypeName = ANSI_TO_TCHAR(THotPatcherTemplateHelper::GetCPPTypeName().c_str()); + FoundEnum = FindObject(ANY_PACKAGE, *EnumTypeName, true); +#endif + if (FoundEnum) + { + result = FoundEnum->GetNameByValue((int64)InEnumValue).ToString(); + result.Split(TEXT("::"), &TypeName, &ValueName, ESearchCase::CaseSensitive, ESearchDir::FromEnd); + if (!bFullName) + { + result = ValueName; + } + } + } + return result; + } + + + template + bool GetEnumValueByName(const FString& InEnumValueName, ENUM_TYPE& OutEnumValue) + { + SCOPED_NAMED_EVENT_TEXT("GetEnumValueByName",FColor::Red); + bool bStatus = false; + UEnum* FoundEnum = nullptr; + FString EnumTypeName; + +#if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 21 + FoundEnum = StaticEnum(); + EnumTypeName = FoundEnum->CppType; +#else + EnumTypeName = ANSI_TO_TCHAR(THotPatcherTemplateHelper::GetCPPTypeName().c_str()); + FoundEnum = FindObject(ANY_PACKAGE, *EnumTypeName, true); +#endif + + if (FoundEnum) + { + FString EnumValueFullName = EnumTypeName + TEXT("::") + InEnumValueName; + int32 EnumIndex = FoundEnum->GetIndexByName(FName(*EnumValueFullName)); + if (EnumIndex != INDEX_NONE) + { + int32 EnumValue = FoundEnum->GetValueByIndex(EnumIndex); + ENUM_TYPE ResultEnumValue = (ENUM_TYPE)EnumValue; + OutEnumValue = ResultEnumValue; + bStatus = true; + } + } + return bStatus; + } + + template + static bool TSerializeStructAsJsonObject(const TStructType& InStruct,TSharedPtr& OutJsonObject) + { + SCOPED_NAMED_EVENT_TEXT("TSerializeStructAsJsonObject",FColor::Red); + if(!OutJsonObject.IsValid()) + { + OutJsonObject = MakeShareable(new FJsonObject); + } + bool bStatus = FJsonObjectConverter::UStructToJsonObject(TStructType::StaticStruct(),&InStruct,OutJsonObject.ToSharedRef(),0,0); + return bStatus; + } + + template + static bool TDeserializeJsonObjectAsStruct(const TSharedPtr& OutJsonObject,TStructType& InStruct) + { + SCOPED_NAMED_EVENT_TEXT("TDeserializeJsonObjectAsStruct",FColor::Red); + bool bStatus = false; + if(OutJsonObject.IsValid()) + { + bStatus = FJsonObjectConverter::JsonObjectToUStruct(OutJsonObject.ToSharedRef(),TStructType::StaticStruct(),&InStruct,0,0); + } + return bStatus; + } + + static bool SerializeJsonObjAsJsonString(TSharedPtr JsonObject,FString& OutJsonString) + { + SCOPED_NAMED_EVENT_TEXT("SerializeJsonObjAsJsonString",FColor::Red); + bool bRunStatus = false; + if(JsonObject.IsValid()) + { + auto JsonWriter = TJsonWriterFactory<>::Create(&OutJsonString); + FJsonSerializer::Serialize(JsonObject.ToSharedRef(), JsonWriter); + bRunStatus = true; + } + return bRunStatus; + } + + template + static bool TSerializeStructAsJsonString(const TStructType& InStruct,FString& OutJsonString) + { + SCOPED_NAMED_EVENT_TEXT("TSerializeStructAsJsonString",FColor::Red); + bool bRunStatus = false; + + { + TSharedPtr JsonObject; + if (THotPatcherTemplateHelper::TSerializeStructAsJsonObject(InStruct,JsonObject) && JsonObject.IsValid()) + { + auto JsonWriter = TJsonWriterFactory<>::Create(&OutJsonString); + FJsonSerializer::Serialize(JsonObject.ToSharedRef(), JsonWriter); + bRunStatus = true; + } + } + return bRunStatus; + } + + template + static bool TDeserializeJsonStringAsStruct(const FString& InJsonString,TStructType& OutStruct) + { + SCOPED_NAMED_EVENT_TEXT("TDeserializeJsonStringAsStruct",FColor::Red); + bool bRunStatus = false; + TSharedRef> JsonReader = TJsonReaderFactory::Create(InJsonString); + TSharedPtr DeserializeJsonObject; + if (FJsonSerializer::Deserialize(JsonReader, DeserializeJsonObject)) + { + bRunStatus = THotPatcherTemplateHelper::TDeserializeJsonObjectAsStruct(DeserializeJsonObject,OutStruct); + } + return bRunStatus; + } + + static TMap GetCommandLineParamsMap(const FString& CommandLine) + { + TMap resault; + TArray ParamsSwitchs, ParamsTokens; + FCommandLine::Parse(*CommandLine, ParamsTokens, ParamsSwitchs); + + for (const auto& SwitchItem : ParamsSwitchs) + { + TArray SwitchArray; + SwitchItem.ParseIntoArray(SwitchArray,TEXT("="),true); + if(SwitchArray.Num()>1) + { + resault.Add(SwitchArray[0],SwitchArray[1].TrimQuotes()); + } + else + { + resault.Add(SwitchArray[0],TEXT("")); + } + } + return resault; + } + + static bool HasPrroperty(UStruct* Field,const FString& FieldName) + { + SCOPED_NAMED_EVENT_TEXT("HasPrroperty",FColor::Red); + for(TFieldIterator PropertyIter(Field);PropertyIter;++PropertyIter) + { + if(PropertyIter->GetName().Equals(FieldName,ESearchCase::IgnoreCase)) + { + return true; + } + } + return false; + } + + static bool ConvStr2Bool(const FString& Str,bool& bOut) + { + bool bIsTrue = Str.Equals(TEXT("true"),ESearchCase::IgnoreCase); + bool bIsFlase = Str.Equals(TEXT("false"),ESearchCase::IgnoreCase); + if(bIsTrue) { bOut = true; } + if(bIsFlase){ bOut = false; } + return bIsTrue || bIsFlase; + } + + template + static void ReplaceProperty(T& Struct, const TMap& ParamsMap) + { + SCOPED_NAMED_EVENT_TEXT("ReplaceProperty",FColor::Red); + TSharedPtr DeserializeJsonObject; + THotPatcherTemplateHelper::TSerializeStructAsJsonObject(Struct,DeserializeJsonObject); + if (DeserializeJsonObject.IsValid()) + { + TArray MapKeys; + ParamsMap.GetKeys(MapKeys); + + for(const auto& key:MapKeys) + { + TArray BreakedDot; + key.ParseIntoArray(BreakedDot,TEXT(".")); + if(BreakedDot.Num()) + { + TSharedPtr JsonObject = DeserializeJsonObject; + if(HasPrroperty(T::StaticStruct(),BreakedDot[0])) + { + int32 lastIndex = BreakedDot.Num()-1; + for(int32 index=0;indexGetObjectField(BreakedDot[index]); + } + + if(JsonObject) + { + FString Value = *ParamsMap.Find(key); + FString From = JsonObject->GetStringField(BreakedDot[lastIndex]); + JsonObject->SetStringField(BreakedDot[lastIndex],Value); + UE_LOG(LogTemp,Display,TEXT("ReplaceProperty %s from %s to %s."),*BreakedDot[0],*From,*Value); + } + } + } + } + FString JsonStr; + THotPatcherTemplateHelper::SerializeJsonObjAsJsonString(DeserializeJsonObject,JsonStr); + THotPatcherTemplateHelper::TDeserializeJsonObjectAsStruct(DeserializeJsonObject,Struct); + } + } + + template + static TArray> SplitArray(const TArray& Array,int32 SplitNum) + { + SCOPED_NAMED_EVENT_TEXT("SplitArray",FColor::Red); + TArray> result; + result.AddDefaulted(SplitNum); + + for( int32 index=0; index= Array.Num()) + { + break; + } + } + } + return result; + } + + template + TArray GetArrayBySrcWithCondition(TArray& SrcArray, TFunction Matcher, bool RemoveFromSrc) + { + SCOPED_NAMED_EVENT_TEXT("GetArrayBySrcWithCondition",FColor::Red); + TArray result; + for(int32 Index = SrcArray.Num() - 1 ;Index >= 0;--Index) + { + if(Matcher(SrcArray[Index])) + { + result.Add(SrcArray[Index]); + if(RemoveFromSrc) + { + SrcArray.RemoveAtSwap(Index,1,false); + } + } + } + return result; + } + + typedef TArray> FEnumeratorPair; + template + FEnumeratorPair AppendEnumeraters(const TArray& Enmueraters) + { + UEnum* UEnumIns = THotPatcherTemplateHelper::GetUEnum(); + uint64 MaxEnumValue = UEnumIns->GetMaxEnumValue()-2; + FString EnumName = UEnumIns->GetName(); + FEnumeratorPair EnumNamePairs; + + TArray AppendEnumsCopy = Enmueraters; + + for (T EnumeraterName:TEnumRange()) + { + FName EnumtorName = UEnumIns->GetNameByValue((int64)EnumeraterName); + EnumNamePairs.Emplace(EnumtorName,(int64)EnumeraterName); + AppendEnumsCopy.Remove(EnumtorName.ToString()); + } + for(const auto& AppendEnumItem:AppendEnumsCopy) + { + ++MaxEnumValue; + EnumNamePairs.Emplace( + FName(*FString::Printf(TEXT("%s::%s"),*EnumName,*AppendEnumItem)), + MaxEnumValue + ); + } +#if ENGINE_MAJOR_VERSION > 4 || ENGINE_MINOR_VERSION > 25 + UEnumIns->SetEnums(EnumNamePairs,UEnum::ECppForm::EnumClass,EEnumFlags::None,true); +#else + UEnumIns->SetEnums(EnumNamePairs,UEnum::ECppForm::EnumClass,true); +#endif + return EnumNamePairs; + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..f06e0c9 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +## HotPatcher +**重要通知:作者没有任何平台和渠道录制收费课程,也不对任何第三方的商业行为背书。请擦亮双眼,谨防上当受骗。** + +**本软件的开源协议:允许在商业项目中免费使用功能,但不允许任何第三方基于该插件进行任何形式的二次收费,包括但不限于录制收费课程、对插件及代码的二次分发等。** + +English Document:[README_EN.md](https://github.com/hxhb/HotPatcher/blob/master/README_EN.md) + +[HotPatcher](https://github.com/hxhb/HotPatcher)是虚幻引擎中涵盖了热更新、构建提升、包体优化、资源审计等全方面流程的资源管理框架。 + +在热更新领域,HotPatcher可以管理热更版本和资源打包,能够追踪工程版本的原始资源变动。支持资源版本管理、版本间的差异对比和打包、支持导出多平台的基础包信息、方便地Cook和打包多平台的Patch,并提供了数种开箱即用的包体优化方案,支持迭代打包、丰富的配置化选项,全功能的commandlet支持,可以非常方便地与ci/cd平台相集成。全平台、跨引擎版本(UE4.21 ~ UE5)支持。 + +>目前支持的引擎版本为UE4.21-UE5(已支持UE5.1.0),我创建了个群来讨论UE热更新和HotPatcher插件的问题(QQ群958363331)。 + +插件文档:[UE资源热更打包工具HotPatcher](https://imzlp.com/posts/17590/) + +视频教程:[UE4热更新:HotPatcher插件使用教程](https://www.bilibili.com/video/BV1Tz4y197tR/) + +我的UOD热更新主题演讲: +- [Unreal Open Day2022 UE热更新的原理与实现 | 查利鹏](https://www.bilibili.com/video/BV1d841187Pt/?zw) +- [Unreal Open Day2020 虚幻引擎4全平台热更新方案 | 查利鹏](https://www.bilibili.com/video/BV1ir4y1c76g) + +我写的UE热更新和资源管理的系列文章,可以作为工程实践的参考: + +- [UE热更新:需求分析与方案设计](https://imzlp.com/posts/17371) +- [UE资源热更打包工具HotPatcher](https://imzlp.com/posts/17590/) +- [UE热更新:基于HotPatcher的自动化流程](https://imzlp.com/posts/10938/) +- [2020 Unreal Open Day](https://imzlp.com/posts/11043/) +- [UE热更新:拆分基础包](https://imzlp.com/posts/13765/) +- [UE热更新:资产管理与审计工具](https://imzlp.com/posts/3675) +- [UE热更新:Create Shader Patch](https://imzlp.com/posts/5867/) +- [UE热更新:Questions & Answers](https://imzlp.com/posts/16895/) +- [UE热更新:资源的二进制补丁方案](https://imzlp.com/posts/25136/) +- [UE热更新:Shader更新策略](https://imzlp.com/posts/15810/) +- [UE 资源合规检查工具 ResScannerUE](https://imzlp.com/posts/11750/) +- [UE热更新:Config的重载与应用](https://imzlp.com/posts/9028/) +- [基于ResScannerUE的资源检查自动化实践](https://imzlp.com/posts/20376/) +- [UE5:Game Feature预研](https://imzlp.com/posts/17658/) +- [UE 资源管理:引擎打包资源分析](https://imzlp.com/posts/22570/) +- [基于ZSTD字典的Shader压缩方案](https://imzlp.com/posts/24725/) +- [虚幻引擎中 Pak 的运行时重组方案](https://imzlp.com/posts/12188/) +- [一种灵活与非侵入式的基础包拆分方案](https://imzlp.com/posts/24350/) + +**基于HotPatcher的资源管理框架** + +![](https://img.imzlp.com/imgs/zlp/picgo/2021/20220526194731.png) + +**应用项目** +| QQ | Apex Legends Mobile | MOSSAI 元宇宙 | +| :----------------------------------------------------------: | :----------------------------------------------------------: | :-------------: | +| | | | + +HotPatcher在大量的UE项目中使用,是目前UE社区中最流行的热更新工具。 \ No newline at end of file diff --git a/README_EN.md b/README_EN.md new file mode 100644 index 0000000..74c4d34 --- /dev/null +++ b/README_EN.md @@ -0,0 +1,49 @@ +## UE4 Plugin: HotPatcher +**Important notice**: The author does not have any platforms and channels to record paid courses, nor does he endorse any third-party business practices. Please keep your eyes open and beware of being deceived. + +**The open source agreement of this software**: It allows free use of functions in commercial projects, but does not allow any third party to make any form of secondary charges based on the plug-in, Including but not limited to recording paid courses, secondary distribution of plug-ins and codes, etc. + +Chinese Document:[README.md](https://github.com/hxhb/HotPatcher/blob/master/README.md). + +[HotPatcher](https://github.com/hxhb/HotPatcher) is a tool for managing hot update versions and resource packaging. It is used to track changes in the original resources of the project version to create patches. Support resource version management, difference comparison and packaging between versions, support exporting basic package information for multiple platforms, easily cook and package multi-platform Patches, support iterative packaging, rich configuration options, full-featured commandlet support, can be combined with ci/cd platform is integrated. + +>The currently supported engine version is UE4.21-UE5 (UE5.1.0 is supported). I created a group to discuss UE4 hot update and HotPatcher plug-in issues (QQ group 958363331). + +Plug-in documentation: [UE4 resource hot update packaging tool HotPatcher](https://imzlp.com/posts/17590/) + +Video tutorial: [UE4 hot update: HotPatcher plug-in tutorial](https://www.bilibili.com/video/BV1Tz4y197tR/) + +My UOD Hot Update Keynote Speech: +- [Unreal Open Day2020 Principle and Implementation of UE Hot Update | lipengzha](https://www.bilibili.com/video/BV1d841187Pt/?zw) +- [Unreal Open Day2020 Unreal Engine 4 Full Platform Hot Update Solution | lipengzha](https://www.bilibili.com/video/BV1ir4y1c76g) + +The series of UE4 hot update articles I wrote can be used as a reference for engineering practice: + +- [UE4 Hot Update: Demand Analysis and Scheme Design](https://imzlp.com/posts/17371) +- [UE4 resource packaging tool HotPatcher](https://imzlp.com/posts/17590/) +- [UE4 Hot Update: Automated Process Based on HotPatcher](https://imzlp.com/posts/10938/) +- [2020 Unreal Open Day](https://imzlp.com/posts/11043/) +- [UE4 Hot Update: Split the basic package](https://imzlp.com/posts/13765/) +- [UE4 Hot Update: Asset Management and Audit Tool](https://imzlp.com/posts/3675) +- [UE4 Hot Update: Create Shader Patch](https://imzlp.com/posts/5867/) +- [UE4 Hot Update: Questions & Answers](https://imzlp.com/posts/16895/) +- [UE Hot Update: Binary Patch Solution for Resources](https://imzlp.com/posts/25136/) +- [UE Hot Update: Shader Update Strategy](https://imzlp.com/posts/15810/) +- [UE Hot Update: Reload and Reapply of Config](https://imzlp.com/posts/9028/) +- [Resource Inspection Automation Practice Based on ResScannerUE](https://imzlp.com/posts/20376/) +- [UE5: Game Feature Pre-Research](https://imzlp.com/posts/17658/) +- [UE Resource Management: Engine Packaging Resource Analysis](https://imzlp.com/posts/22570/) +- [Shader compression scheme based on ZSTD dictionary](https://imzlp.com/posts/24725/) +- [Runtime reorganization scheme for Pak in Unreal Engine](https://imzlp.com/posts/12188/) +- [A Flexible and Non-Intrusive Basic Package Splitting Scheme](https://imzlp.com/posts/24350/) + +**Resource management framework based on HotPatcher** + +![](https://img.imzlp.com/imgs/zlp/picgo/2021/20220526194731.png) + +**Application project** +| QQ | Apex Legends Mobile | MOSSAI| +| :----------------------------------------------------------: | :----------------------------------------------------------: | :-------------: | +| | | | + +HotPatcher is used in a large number of UE projects and is currently the most popular hot update tool in the UE community. \ No newline at end of file