[{"data":1,"prerenderedAt":1058},["ShallowReactive",2],{"page-UnrealPak-1":3,"page-count-UnrealPak":53},[4,555],{"id":5,"title":6,"body":7,"date":532,"description":13,"extension":533,"meta":534,"navigation":104,"path":548,"seo":549,"stem":550,"tags":551,"__hash__":554},"blogs\u002F_legacy\u002F2017\u002F2017-06-24-unrealpak-note.md","UnrealPak的使用笔记",{"type":8,"value":9,"toc":509},"minimark",[10,14,22,27,30,33,172,175,180,183,193,197,200,203,207,210,214,217,220,224,227,230,232,236,239,242,245,249,252,254,257,260,263,266,269,272,275,279,282,285,288,291,294,297,300,303,306,314,317,323,326,332,335,344,350,353,359,362,368,371,374,377,381,389,392,396,402,405,409,412,418,421,424,427,430,433,440,443,446,449,455,458,464,467,473,476,479,485,488,491,494,497,505],[11,12,13],"p",{},"UE4的打包可以通过编辑器直接进行，但是当要实现一些特殊的目标，例如热更新等时，就需要自己使用UnrealPak进行才行。",[11,15,16,17,21],{},"当前使用UE4版本4.16，所有打包",[18,19,20],"strong",{},"仅","在Shiping模式下测试过。",[23,24,26],"h2",{"id":25},"unrealpak","UnrealPak",[11,28,29],{},"UnrealPak在整个打包过程中位于最后面的一个过程，在编辑器中打包的话可以通过输出判断打包的各个阶段。在编译、烘焙之后，如果有勾选Use Pak这个选项的话，引擎就会尝试将以上两个过程中生成的内容打包到Pak中去。",[11,31,32],{},"UnrealPak.exe位于引擎目录下Engine\u002FWin64中，运行程序，在错误提示中有给出基本的使用方法：",[34,35,40],"pre",{"className":36,"code":37,"language":38,"meta":39,"style":39},"language-shell shiki shiki-themes github-light-high-contrast github-dark monokai","UnrealPak \u003CPakFilename> -Test\nUnrealPak \u003CPakFilename> -List\nUnrealPak \u003CPakFilename> -Extract \u003CExtractDir>\nUnrealPak \u003CPakFilename> -Create=\u003CResponseFile> [Options]\nUnrealPak \u003CPakFilename> -Dest=\u003CMountPoint>\nUnrealPak GenerateKeys=\u003CKeyFilename>\nUnrealPak GeneratePrimeTable=\u003CKeyFilename> [-TableMax=\u003CN>]\nUnrealPak \u003CPakFilename1> \u003CPakFilename2> -diff\nUnrealPak -TestEncryption\n\nOptions:\n-blocksize=\u003CBlockSize>\n-bitwindow=\u003CBitWindow>\n-compress\n-encrypt\n-order=\u003COrderingFile>\n-diff (requires 2 filenames first)\n-enginedir (specify engine dir for when using ini encryption configs)\n-projectdir (specify project dir for when using ini encryption configs)\n-encryptionini (specify ini base name to gather encryption settings from)\n-encryptindex (encrypt the pak file index, making it unusable in unrealpak without supplying the key)\n","shell","",[41,42,43,51,57,63,69,75,81,87,93,99,106,112,118,124,130,136,142,148,154,160,166],"code",{"__ignoreMap":39},[44,45,48],"span",{"class":46,"line":47},"line",1,[44,49,50],{},"UnrealPak \u003CPakFilename> -Test\n",[44,52,54],{"class":46,"line":53},2,[44,55,56],{},"UnrealPak \u003CPakFilename> -List\n",[44,58,60],{"class":46,"line":59},3,[44,61,62],{},"UnrealPak \u003CPakFilename> -Extract \u003CExtractDir>\n",[44,64,66],{"class":46,"line":65},4,[44,67,68],{},"UnrealPak \u003CPakFilename> -Create=\u003CResponseFile> [Options]\n",[44,70,72],{"class":46,"line":71},5,[44,73,74],{},"UnrealPak \u003CPakFilename> -Dest=\u003CMountPoint>\n",[44,76,78],{"class":46,"line":77},6,[44,79,80],{},"UnrealPak GenerateKeys=\u003CKeyFilename>\n",[44,82,84],{"class":46,"line":83},7,[44,85,86],{},"UnrealPak GeneratePrimeTable=\u003CKeyFilename> [-TableMax=\u003CN>]\n",[44,88,90],{"class":46,"line":89},8,[44,91,92],{},"UnrealPak \u003CPakFilename1> \u003CPakFilename2> -diff\n",[44,94,96],{"class":46,"line":95},9,[44,97,98],{},"UnrealPak -TestEncryption\n",[44,100,102],{"class":46,"line":101},10,[44,103,105],{"emptyLinePlaceholder":104},true,"\n",[44,107,109],{"class":46,"line":108},11,[44,110,111],{},"Options:\n",[44,113,115],{"class":46,"line":114},12,[44,116,117],{},"-blocksize=\u003CBlockSize>\n",[44,119,121],{"class":46,"line":120},13,[44,122,123],{},"-bitwindow=\u003CBitWindow>\n",[44,125,127],{"class":46,"line":126},14,[44,128,129],{},"-compress\n",[44,131,133],{"class":46,"line":132},15,[44,134,135],{},"-encrypt\n",[44,137,139],{"class":46,"line":138},16,[44,140,141],{},"-order=\u003COrderingFile>\n",[44,143,145],{"class":46,"line":144},17,[44,146,147],{},"-diff (requires 2 filenames first)\n",[44,149,151],{"class":46,"line":150},18,[44,152,153],{},"-enginedir (specify engine dir for when using ini encryption configs)\n",[44,155,157],{"class":46,"line":156},19,[44,158,159],{},"-projectdir (specify project dir for when using ini encryption configs)\n",[44,161,163],{"class":46,"line":162},20,[44,164,165],{},"-encryptionini (specify ini base name to gather encryption settings from)\n",[44,167,169],{"class":46,"line":168},21,[44,170,171],{},"-encryptindex (encrypt the pak file index, making it unusable in unrealpak without supplying the key)\n",[11,173,174],{},"整个程序大致的功能就是可以解压和打包Pak文件：",[176,177,179],"h3",{"id":178},"generatekeys","GenerateKeys",[11,181,182],{},"生成加密Key到文件中，RSA用于对Pak进行签名。",[184,185,186,190],"ul",{},[187,188,189],"li",{},"P：用于RSA的素数p",[187,191,192],{},"Q：用于RSA的素数q",[176,194,196],{"id":195},"generateprimetable","GeneratePrimeTable",[11,198,199],{},"生成素数表，",[11,201,202],{},"TableMax：生成表的停止（最大）数值，默认值10000",[176,204,206],{"id":205},"testencryption","TestEncryption",[11,208,209],{},"测试加密算法的执行，并不检测具体的Key和文件。",[176,211,213],{"id":212},"test","TEST",[11,215,216],{},"对Pak文件进行检测，会对每一个文件进行SHA1校验。",[11,218,219],{},"signed：Pak是否已签名",[176,221,223],{"id":222},"list","List",[11,225,226],{},"列出文件。",[11,228,229],{},"SizeFilter：文件的大小过滤器，会对大于这个大小的文件进行总Size的统计。",[11,231,219],{},[176,233,235],{"id":234},"diff","Diff",[11,237,238],{},"对比两个Pak文件",[11,240,241],{},"nouniques：通过指定nouniquesfile1、nouniquesfile2，是否列出Unique的文件",[11,243,244],{},"signed：Pak是否已签名，不能分开指定。",[176,246,248],{"id":247},"extract","Extract",[11,250,251],{},"解包。",[11,253,219],{},[176,255,256],{"id":256},"构建",[11,258,259],{},"blocksize：块大小，MB或KB单位",[11,261,262],{},"bitwindow：压缩的窗口大小，这个值越大压缩的效果越好，同时消耗的性能也越高。",[11,264,265],{},"patchpaddingalign：是否在进行Patch时启用对齐，不patch时对齐时自动的。",[11,267,268],{},"encryptindex：加密索引，使得文件索引在不提供key的情况下无法解密。",[11,270,271],{},"order：文件读取顺序列表，这个order在cook时会自动生成，一般不用自己提供。uexp会被加上序列值1 \u003C\u003C 29和ubulk会被加上1 \u003C\u003C 30。没有提供order的其他文件将获得 (1 \u003C\u003C 28)。",[11,273,274],{},"sign：对Pak进行签名的RSA的key",[276,277,278],"h4",{"id":278},"create",[11,280,281],{},"文件列表，可在其中单独指定compress和encrypt，但是如果在命令行中指定了的话，就会被强制使用到所有文件。可以是目录或是使用*通配符。",[11,283,284],{},"generatepatch：生成Patch |同时可使用 TempFiles来指定临时目录",[11,286,287],{},"compress：压缩",[11,289,290],{},"encrypt：加密",[276,292,293],{"id":293},"dest",[11,295,296],{},"替换Pak中的MountPoint。",[176,298,299],{"id":299},"加密和压缩",[11,301,302],{},"Pak采用RSA进行签名，AES进行加密，SHA1进行校验。",[11,304,305],{},"加密的流程没有测试，加密的参数可以在ini文件Encryption中进行设置，要使用ini配置的话，需要指定-encryptionini。或者，可以直接在命令行中提供加密参数：",[34,307,312],{"className":308,"code":310,"language":311},[309],"language-text","-Sign=filename\n\n","text",[41,313,310],{"__ignoreMap":39},[11,315,316],{},"或者",[34,318,321],{"className":319,"code":320,"language":311},[309],"-sign=0x123456789abcdef+0x1234567+0x12345abc\n",[41,322,320],{"__ignoreMap":39},[11,324,325],{},"其中第一个数是Exponent，第二个数是Module，第三个数是公钥的Exponent",[34,327,330],{"className":328,"code":329,"language":311},[309],"-aes=blabla\n",[41,331,329],{"__ignoreMap":39},[11,333,334],{},"来提供AES加密秘钥。",[11,336,337,338,343],{},"当前的ProjectLauncher打包，会自动添加-encryptionini，但是只要没有提供Encryption.ini，或者[",[339,340,342],"a",{"href":341},"\u002F2017-06-06-ue4-config-usage\u002F#i","Ini配置","]不正确，就不会进行加密。配置文件的内容应该是类似这样的：",[34,345,348],{"className":346,"code":347,"language":311},[309],"[Core.Encryption]\nSignPak=True\nEncryptPak=True\nrsa.publicexp=blablabla\nrsa.privateexp=blablabla\nrsa.modulus=blablabla\naes.key=blablabla\n",[41,349,347],{"__ignoreMap":39},[11,351,352],{},"由于没有实际测试过，所以不是很明确Encryption的配置能否被打包后的程序自动加载。如果出现了问题，可以检查下CoreDelegate中的：",[34,354,357],{"className":355,"code":356,"language":311},[309],"static FPakEncryptionKeyDelegate& GetPakEncryptionKeyDelegate();\nstatic FPakSigningKeysDelegate& GetPakSigningKeysDelegate();\n",[41,358,356],{"__ignoreMap":39},[11,360,361],{},"引擎在Mount时会从这两个Delegate中获取加密相关的信息，在CoreDelegate的Cpp中有提供",[34,363,366],{"className":364,"code":365,"language":311},[309],"void RegisterEncryptionKey(const char* InEncryptionKey)\n{\n  FCoreDelegates::GetPakEncryptionKeyDelegate().BindLambda([InEncryptionKey]() { return InEncryptionKey; });\n}\n\nvoid RegisterPakSigningKeys(const char* InExponent, const char* InModulus)\n{\n  static FString Exponent(ANSI_TO_TCHAR(InExponent));\n  static FString Modulus(ANSI_TO_TCHAR(InModulus));\n\nFCoreDelegates::GetPakSigningKeysDelegate().BindLambda([](FString& OutExponent, FString& OutModulus)\n{\n  OutExponent = Exponent;\n  OutModulus = Modulus;\n});\n}\n",[41,367,365],{"__ignoreMap":39},[11,369,370],{},"如果没有办法成功注册解密的话，可以尝试模仿引擎代码进行注册来调试一下。",[11,372,373],{},"不过事实上这些功能如果只是打包的话基本不用深究，在使用Project Launcer进行打包时，可以看到编辑器对UnrealPak的调用，将其复制出来，然后进行相应的修改就可以了。",[11,375,376],{},"在进行测试的时候需要注意，UE4对于Asset的加载是有要求的，如果是Shiping烘焙的内容，是无法在编辑器中加载的，必须统一内容的打包格式。",[23,378,380],{"id":379},"mount","Mount",[11,382,383,384,388],{},"Pak在运行时通过Mount将内容读取到游戏中，如果不希望Pak文件被自动加载的话，可以不将其放到[",[339,385,387],{"href":386},"\u002F2017-05-01-ue4-patch-release-dlc\u002F#Maps","自动加载目录","]中。",[11,390,391],{},"手动加载Pak文件时，通常采用的Mount方法有两种。",[176,393,395],{"id":394},"手动mount","手动Mount",[34,397,400],{"className":398,"code":399,"language":311},[309],"EL_LOG(TEXT(\"[AShipIssueGameModeBase] LoadPakMount(): %s\"), *PakPath);\nFString SaveContentDir = PakPath;\nUE_LOG(LogTemp, Log, TEXT(\"[AShipIssueGameModeBase]  ----------- LoadPak: %s --------------\"), *SaveContentDir);\n\n\u002F\u002F获取当前使用的平台,这里使用的是WIN64平台\nIPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();\n\u002F\u002F初始化PakPlatformFile\nFPakPlatformFile* PakPlatformFile = new FPakPlatformFile();\nPakPlatformFile->Initialize(&PlatformFile, TEXT(\"\"));\nFPlatformFileManager::Get().SetPlatformFile(*PakPlatformFile);\n\n\u002F\u002F获取Pak文件\nFPakFile PakFile(&PlatformFile, *SaveContentDir, false);\n\n\u002F\u002F设置pak文件的Mount点.\nFString MountPoint(\"..\u002F..\u002F..\u002F[ProjectName]\u002F\");\nUE_LOG(LogTemp, Log, TEXT(\"[AShipIssueGameModeBase] - Mount Path: %s\"), *MountPoint);\nEL_LOG(TEXT(\"[AShipIssueGameModeBase] Mount Path: %s\"), *MountPoint);\n\nPakFile.SetMountPoint(*MountPoint);\n\u002F\u002F对pak文件mount到前面设定的MountPoint\nif (PakPlatformFile->Mount(*SaveContentDir, 0, *MountPoint))\n{\n  UE_LOG(LogTemp, Log, TEXT(\"[AShipIssueGameModeBase] - Mount Success\"));\n  EL_LOG(TEXT(\"[AShipIssueGameModeBase] Mount Success\"));\n\n}\n",[41,401,399],{"__ignoreMap":39},[11,403,404],{},"这个方法可以手动指定MountPoint，但是通常情况下，除非很确定自己打包了什么，否则很容易丢失各个Asset之间的引用。",[176,406,408],{"id":407},"coredelegate","CoreDelegate",[11,410,411],{},"这个是最近的版本添加的帮助函数，比上面的方法更加的简单实用。",[34,413,416],{"className":414,"code":415,"language":311},[309],"if (FCoreDelegates::OnMountPak.IsBound())\n{\n  if (FCoreDelegates::OnMountPak.Execute(PakPath, 4, nullptr))\n  {\n     EL_LOG(TEXT(\"[AShipIssueGameModeBase] LoadPakDelegate(): OnMountPak.Execute Successful.\"));\n     GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT(\"OnMountPak.Execute Successful.\"));\n  }\n  else\n  {\n    EL_LOG(TEXT(\"[AShipIssueGameModeBase] LoadPakDelegate(): OnMountPak.Execute Falied.\"));\n    GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT(\"OnMountPak.Execute Falied.\"));\n  }\n}\nelse\n{\n  EL_LOG(TEXT(\"[AShipIssueGameModeBase] LoadPakDelegate(): OnMountPak.IsBound() Falied\"));\n  GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT(\"OnMountPak.IsBound() Falied\"));\n}\n",[41,417,415],{"__ignoreMap":39},[11,419,420],{},"使用这个方法会将内容Mount到内容根目录，引擎内部加载Pak文件也是使用的这个方法。",[23,422,423],{"id":423},"路径与引用",[11,425,426],{},"一般情况下使用同一个项目打包的Pak文件，只要打包正确，进行Mount都不会有什么问题。",[11,428,429],{},"而如果使用其他项目进行打包的话，可能会存在一些引用丢失的问题。通常的表现是，使用Mount后的路径可以打开地图，但是地图内物品的材质等关联属性全部都丢失了。",[11,431,432],{},"这个问题通常都是路径问题引起的，另外，在Cook目录中会有AssetRegistry，这个文件是UE用来保存各个Asset之间的引用的，在网上有看到Mount时要自己加载并注册的描述，但是目前版本测试下似乎是会自动加载的。至少在没有主动加载AssetRegistry的情况下，引用已经是正常的了，但是如果出现了引用丢失的问题，可以从这个方面进行一下检查。而且使用CoreDelegate进行Mount的话，是不需要关心这个的。",[11,434,435,436,439],{},"在打包时主要的路径问题来自于打包时的处理机制，UnrealPak会尝试从所有待打包的文件中提取公共的目录作为根目录。而这个根目录之后会被挂在MountPoint之下，那么如果配置有不正确的话，必然会丢失引用。如果引用丢失了的话，对打包的根目录检查也是必要的。在使用",[41,437,438],{},"-create=\u003CResponsFile>","进行打包时，可以在每个文件列的后面指定打包的目标目录，可以一定程度的上解决这个问题。",[176,441,442],{"id":442},"实际使用",[11,444,445],{},"注意：这里记录的是目前项目中使用的路径，并不代表这是一个“好”的方法。因为UnrealPak自主打包无论如何都不会优雅，如果没有紧急的打包需求，建议等官方的Chunk机制等一系列打包功能完善之后直接使用~",[11,447,448],{},"首先，对目标项目ShipIssue进行打包，从Log中拷贝UnrealPak的记录：",[34,450,453],{"className":451,"code":452,"language":311},[309],"D:\\Code\\UE_4.16\\Engine\\Binaries\\Win64\\UnrealPak.exe F:\\Ue_Patch\\1.0_nomap\\WindowsNoEditor\\ShipIssue\\Content\\Paks\\ShipIssue-WindowsNoEditor.pak -create=\"C:\\Users\\Administrator\\AppData\\Roaming\\Unreal Engine\\AutomationTool\\Logs\\D+Code+UE_4.16\\PakList_ShipIssue-WindowsNoEditor.txt\" -encryptionini -enginedir=\"D:\\Code\\UE_4.16\\Engine\" -projectdir=\"E:\\GameDev\\ShipIssue\" -platform=Windows -abslog=\"C:\\Users\\Administrator\\AppData\\Roaming\\Unreal Engine\\AutomationTool\\Logs\\D+Code+UE_4.16\\PakLog_ShipIssue-WindowsNoEditor.log\" -installed -order=E:\\GameDev\\ShipIssue\\Build\\WindowsNoEditor\\FileOpenOrder\\CookerOpenOrder.log -UTF8Output -multiprocess -patchpaddingalign=2048\n",[41,454,452],{"__ignoreMap":39},[11,456,457],{},"然后，将要打包的项目FreePatch进行Cook之后，对拷贝的命令修改：",[34,459,462],{"className":460,"code":461,"language":311},[309],"D:\\Code\\UE_4.16\\Engine\\Binaries\\Win64\\UnrealPak.exe F:\\Ue_Patch\\1.0_nomap\\WindowsNoEditor\\ShipIssue\\Content\\Paks\\Free.pak -create=\"E:\\PakScript\\list.txt\" -encryptionini -enginedir=\"D:\\Code\\UE_4.16\\Engine\" -projectdir=\"E:\\GameDev\\ShipIssue\" -platform=Windows -abslog=\"C:\\Users\\Administrator\\AppData\\Roaming\\Unreal Engine\\AutomationTool\\Logs\\D+Code+UE_4.16\\PakLog_ShipIssue-WindowsNoEditor.log\" -installed -order=E:\\GameDev\\FreePatch\\Build\\WindowsNoEditor\\FileOpenOrder\\CookerOpenOrder.log -UTF8Output -multiprocess -patchpaddingalign=2048\n",[41,463,461],{"__ignoreMap":39},[11,465,466],{},"Order指令的文件可以直接使用Cook出来的，list.txt直接写：",[34,468,471],{"className":469,"code":470,"language":311},[309],"\"E:\\GameDev\\FreePatch\\Saved\\Cooked\\WindowsNoEditor\\FreePatch\\*\" \"..\u002F..\u002F..\u002FShipIssue\u002F*\" –compress\n",[41,472,470],{"__ignoreMap":39},[11,474,475],{},"目标目录写成目标项目的目录，这样的话打包出来的pak文件在自动加载和使用CoreDelegate加载后都能保持正确的引用。",[11,477,478],{},"如果保留原项目名",[34,480,483],{"className":481,"code":482,"language":311},[309],"\"E:\\GameDev\\FreePatch\\Saved\\Cooked\\WindowsNoEditor\\FreePatch\\*\" \"..\u002F..\u002F..\u002FFreePatch\u002F*\" –compress\n",[41,484,482],{"__ignoreMap":39},[11,486,487],{},"需要Mount时自己将MountPoint指定到..\u002F..\u002F..\u002FShipIssue\u002F，自动加载和CoreDelegate加载会加载到..\u002F..\u002F..\u002FFreePatch\u002F导致无法读取到地图中物体引用的材质。",[11,489,490],{},"因此并不推荐这样做~",[11,492,493],{},"通常情况下，只要mount之后的路径在\u002FShipIssue\u002F下的结构与原本在Content\u002F下的结构相同，材质之类的引用就不会丢失。丢失的情况，一般从路径方面检查下先比较好。",[11,495,496],{},"更新：由于Pak的加载确实有些迷，经常会有朋友发邮件或者留言来问我。但是从打包到加载的过程中的可变量太多，我也无法准确的知道每个人遇到的问题。所以在这里将测试打包用到的代码分离成一个插件。",[11,498,499,500,504],{},"其实插件本身并不复杂，只是在使用UFS的API遍历访问而已。主要的作用是可以方便对加载过程的理解和问题的排查。详情请参考[",[339,501,503],{"href":502},"\u002Fpage\u002Fue4-pak-file-view\u002F","UE4文件查看插件","]。",[506,507,508],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}",{"title":39,"searchDepth":53,"depth":59,"links":510},[511,525,529],{"id":25,"depth":53,"text":26,"children":512},[513,514,515,516,517,518,519,520,524],{"id":178,"depth":59,"text":179},{"id":195,"depth":59,"text":196},{"id":205,"depth":59,"text":206},{"id":212,"depth":59,"text":213},{"id":222,"depth":59,"text":223},{"id":234,"depth":59,"text":235},{"id":247,"depth":59,"text":248},{"id":256,"depth":59,"text":256,"children":521},[522,523],{"id":278,"depth":65,"text":278},{"id":293,"depth":65,"text":293},{"id":299,"depth":59,"text":299},{"id":379,"depth":53,"text":380,"children":526},[527,528],{"id":394,"depth":59,"text":395},{"id":407,"depth":59,"text":408},{"id":423,"depth":53,"text":423,"children":530},[531],{"id":442,"depth":59,"text":442},"2017-06-24","md",{"layout":535,"status":536,"published":104,"author":537,"author_login":539,"author_email":540,"wordpress_id":541,"wordpress_url":542,"date_gmt":543,"excerpt":544},"post","publish",{"display_name":538,"login":539,"email":540,"url":39},"风铃","flinkor","flinkor@foxmail.com",1790,"\u002F\u002F?p=1790","2017-06-24 11:37:17 +0000",{"type":8,"value":545},[546],[11,547,13],{},"\u002F2017-06-24-unrealpak-note",{"title":6,"description":13},"_legacy\u002F2017\u002F2017-06-24-unrealpak-note",[552,26,553],"UE4","Pak","Jr-XOewnXJ3-UJQa19DQu02xnktkiNhN_su7wcAU86Y",{"id":556,"title":557,"body":558,"date":1040,"description":562,"extension":533,"meta":1041,"navigation":104,"path":1050,"seo":1051,"stem":1052,"tags":1053,"__hash__":1057},"blogs\u002F_legacy\u002F2017\u002F2017-05-01-ue4-patch-release-dlc.md","UE4补丁与DLC",{"type":8,"value":559,"toc":1025},[560,563,566,569,573,583,586,589,594,597,602,605,610,613,620,625,628,634,643,646,651,654,659,662,667,670,673,676,683,687,690,693,696,701,704,709,712,716,719,722,728,731,734,739,743,746,751,754,759,762,767,770,773,792,795,799,802,805,808,816,819,822,825,830,833,839,842,845,848,851,855,858,861,864,867,878,881,884,887,891,894,900,903,908,911,916,919,922,925,928,933,936,940,943,949,952,955,960,963,966,969,975,978,983,986,989,994,997,1000,1005,1008,1013,1016,1019,1022],[11,561,562],{},"UE4的打包操作本身还是比较简单的，但是考虑到发布之后的升级与补丁，却还是有一些麻烦。",[11,564,565],{},"当前UE4版本为4.16 P1。",[11,567,568],{},"在动手做东西之前，先了解一下UE4本身对于打包系统的架构是很重要的，否则如果到了Release的阶段发现代码设计上很多地方与系统有冲突的话就会很麻烦。就像是等到开始做联网版本的游戏时才发现很多机制、功能的设计都与UE4的Replication架构不符的话就会需要大幅度的代码变更。",[23,570,572],{"id":571},"unreal-frontend","Unreal Frontend",[11,574,575,576,582],{},"虚幻的打包操作在[",[339,577,581],{"href":578,"rel":579},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FEngine\u002FDeployment\u002Findex.html",[580],"nofollow","官方文档","]中有进行描述，一切打包相关的工作，都以Unreal Frontend为核心进行。当然，Unreal Frontend还提供了部署、自动化测试等功能。",[11,584,585],{},"总体而言，Unreal Frontend的功能还不成熟，很多选项的设定让人费解，也没有提供足够的文档说明，所以可能会遇到一些奇怪的BUG。最麻烦的是，到目前为止，DLC系统依然处于WIP状态，使用起来有很多的不便。",[11,587,588],{},"目前主要会使用到的是Project Launcher模块，在下方的Custom Lanch Profiles区域的右边有加号按钮，可以在那里添加自定义的打包配置。配置大致分为以下区域：",[11,590,591],{},[18,592,593],{},"Project",[11,595,596],{},"选择打包操作的目标项目，由于Unreal Frontend的设置是全局的，所以在一个项目中配置了之后在其他项目中也可以使用。",[11,598,599],{},[18,600,601],{},"Build",[11,603,604],{},"选择是否构建，以及构建的模式，并提供了是否构建UAT（Unreal Automation Tool）的选项。UAT主要提供一些自动化测试方面的功能，根据社区一些用户的反馈来看，需要源码版本引擎才能正常的使用。",[11,606,607],{},[18,608,609],{},"Cook",[11,611,612],{},"烘焙相关选项，一般都是用By the book选项然后勾选相关目标平台。",[11,614,615,616,504],{},"进行Release\u002FPatch\u002FDLC打包的操作都在By the book选项下。其他的一些详细的可选项功能可以参考[",[339,617,581],{"href":618,"rel":619},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FEngine\u002FDeployment\u002FCooking\u002Findex.html",[580],[11,621,622],{},[18,623,624],{},"Package",[11,626,627],{},"是否打包以及如何打包，这里需要注意的是当前版本下对DLC进行打包是会出现BUG的，报错如下：",[629,630,631],"blockquote",{},[11,632,633],{},"Program.Main: ERROR: AutomationTool terminated with exception: System.Exception: Couldn't update resource",[11,635,636,637,642],{},"搜索之后发现似乎是一个[",[339,638,641],{"href":639,"rel":640},"https:\u002F\u002Fissues.unrealengine.com\u002Fissue\u002FUE-42880",[580],"BUG","]，因此目前对DLC可以不需要打包，而是自己将生成的文件进行拷贝就可以了。",[11,644,645],{},"打包的生成目录在Saved\\StagedBuilds\\中。",[11,647,648],{},[18,649,650],{},"Archive",[11,652,653],{},"归档，引擎会在打包后将打包好的内容拷贝到指定的目录去。不使用的话不要勾选就可以了。",[11,655,656],{},[18,657,658],{},"Deploy",[11,660,661],{},"部署，提供了几种部署渠道，可以部署到目前可用的设备上。不使用的话可以直接选中Not Deploy。",[11,663,664],{},[18,665,666],{},"Launch",[11,668,669],{},"测试运行的形式，可以对服务端模式、语言环境、初始地图等进行设置。",[23,671,672],{"id":672},"打包模式",[11,674,675],{},"打包模式目前有Release、Patch、DLC三种，其中DLC处于WIP状态，并没有足够的文档支持，而且今后可能会有不少变动。",[11,677,678],{},[679,680],"img",{"alt":681,"src":682},"image","\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fimage_thumb.png",[176,684,686],{"id":685},"release-version","Release Version",[11,688,689],{},"UE4提供的Release的概念是通常的发行版本控制，勾选Create a release version of the game for distribution就可以开启了。而Patch与DLC都是以某个Release版本为基础进行打包的。",[11,691,692],{},"因此使用Release打包的时候就必须填Name of the new release to create，而Path与DLC则必须填写Release version this is based on。",[11,694,695],{},"DLC是不可以选中Create a release version这个选项的，会报错：",[629,697,698],{},[11,699,700],{},"Program.Main: ERROR: AutomationTool terminated with exception: AutomationTool.AutomationException: Can't create a release version at the same time as creating dlc.",[11,702,703],{},"而选中了这个选项还填写based on version的话也会报错：",[629,705,706],{},[11,707,708],{},"Program.Main: ERROR: AutomationTool terminated with exception: AutomationTool.AutomationException: Only staged builds can be paked, use -stage or -skipstage.",[11,710,711],{},"这个错误也会出现在使用Iterative cooking选项时，如果使用这个选项同时又勾选使用Pak的话，根据当前烘焙缓存的状况不同，也会报这个错。",[176,713,715],{"id":714},"generate-patch","Generate patch",[11,717,718],{},"生成补丁，目前一个Release下只能有一个补丁，对于生成的pak文件，补丁中带后缀_P，会被优先加载并覆盖。同样的pak文件，多加一个_P的话优先级就会更高。",[11,720,721],{},"在Mount函数中，可以看到",[34,723,726],{"className":724,"code":725,"language":311},[309],"if ( PakFilename.EndsWith(TEXT(\"_P.pak\")) )\n{\n  PakOrder += 100;\n}\n",[41,727,725],{"__ignoreMap":39},[11,729,730],{},"凡是以_P结尾的pak文件，会获得额外的100优先级。在进行Patch打包时，引擎也会自动的加上这个后缀。",[11,732,733],{},"这里有一个问题，如果勾选了Store all content in a single file的情况下，即便是在不同的Release版本之间，_P的patch也会被优先加载。还没有测试过如果去除了Save package without versions选项是否会有这样的现象。",[11,735,736],{},[679,737],{"alt":681,"src":738},"\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fimage_thumb-1.png",[176,740,742],{"id":741},"iterative-cooking","Iterative cooking",[11,744,745],{},"增量烘焙必须去掉勾选Save package without versions，否则launch按钮会变成灰色，提示：",[629,747,748],{},[11,749,750],{},"unversioned build cannot be incremental",[11,752,753],{},"这里面的Iterative cooking目前只能在同一个Release版本中进行增量烘焙，如果Releasse的版本迁移了的话，是不能在版本之间进行增量烘焙的，强行勾选会被拒绝操作：",[629,755,756],{},[11,757,758],{},"Program.Main: ERROR: AutomationTool terminated with exception: AutomationTool.AutomationException: Can't use iterative cooking \u002F deploy on dlc or patching or creating a release",[11,760,761],{},"官方的说明是这样的：",[629,763,764],{},[11,765,766],{},"The asset registry and pak file will be needed for any future patches or DLC to check against.",[11,768,769],{},"也就是说保存的content注册关系是为了DLC和patch而存在的，并没有说可以作为Release之间的版本迁移的依据。",[11,771,772],{},"使用Iterative cooking时还有一点需要注意，那就是引擎当前无法区分Ship和DevelopMent之间的烘焙结果，如果切换Build模式时没有去掉这个选项的话，就会导致烘焙资源的混杂，出现类似如下错误：",[629,774,775,789],{},[11,776,777,778,781,782,785,786],{},"Assertion failed: GDefaultMaterials",[44,779,780],{},"Domain"," != NULL ",[44,783,784],{},"File:D:\\Build++UE4+Release-4.16+Compile\\Sync\\Engine\\Source\\Runtime\\Engine\\Private\\Materials\\Material.cpp"," ",[44,787,788],{},"Line: 429",[11,790,791],{},"Cannot load default material 'engine-ini:\u002FScript\u002FEngine.Engine.DefaultMaterialName'",[11,793,794],{},"另外，使用增量会导致不在当前选择的地图内容也会被Build进来，但是无论如何，项目设定中的默认地图都是会别build进来的。",[176,796,798],{"id":797},"build-dlc","Build DLC",[11,800,801],{},"UE4中的DLC是以Plugin的形式进行实现的，DLC Name那里填写的就是插件的名称，是不能随便乱填的。",[11,803,804],{},"插件的话在Plugins窗口直接新建就可以了，如果是蓝图项目的话，只会有一个Content Plugin，选择他就可以了。另外，似乎在打包时如果有地图存在的话，没有被地图引用的资源就不会被打包，需要注意。",[11,806,807],{},"打包DLC中如果出现",[629,809,810,813],{},[11,811,812],{},"Cook: LogInit:Display: LogCook:Error: Engine or Game content ..\u002F..\u002F..\u002FEngine\u002FContent\u002FTutorial\u002FFoliage\u002FFoliage_Intro_Tutorial.uasset is being referenced by DLC!",[11,814,815],{},"Cook: LogInit:Display: LogCook:Error: Engine or Game content ..\u002F..\u002F..\u002FEngine\u002FContent\u002FTutorial\u002FSubEditors\u002FDestructibleMeshEditorTutorial.uasset is being referenced by DLC!",[11,817,818],{},"之类的BUG，可以尝试勾选Include engine content。",[11,820,821],{},"关于DLC，一些旧的资料中会提到Plugin形式无法被引擎自动加载，目前测试过已经没有这个现象了。即便是在Release之后添加的Plugin，在做DLC的打包之后，直接扔到项目目录中也会被自动加载。不知是否是因为使用DevelopMent打包的关系。",[11,823,824],{},"由于当前Package DLC不知为何会报错，类似这样",[629,826,827],{},[11,828,829],{},"UATHelper: Packaging (Windows (64-bit)): Program.Main: ERROR: AutomationTool terminated with exception: System.Exception: Couldn't update resource",[11,831,832],{},"其实Pak已经打包成功了，直接Achive即可。",[11,834,835],{},[679,836],{"alt":837,"src":838},"msohtmlclipclip_image0014","\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fmsohtmlclipclip_image0014_thumb.png",[11,840,841],{},"将生成的内容直接复制到打包程序里面，就会被自动加载了，即便Release打包的时候并没有这个DLC的Plugin也是可以的。",[23,843,844],{"id":844},"补丁形式",[11,846,847],{},"官方在Patch的文档中有提到补丁的覆盖优先，由于Patch是从Release的版本开始进行差分的，如果两个Patch都是针对整个游戏的话，那么前一个Patch就没有意义了，可以删除。",[11,849,850],{},"这样一来，为了防止每次更新的内容过大，我们就必须对项目的组织进行设计，目前我们可以通过Maps或者Chunks来做内容差分管理。",[176,852,854],{"id":853},"maps","Maps",[11,856,857],{},"在进行打包时，被打包地图及其所引用的资源有改变的都会被打包，只是是否会被游戏采用取决于Pak的加载优先级。",[11,859,860],{},"而不被选中的地图中，即便相关资源有变动，也不会被打包到Patch中去，但是DefaultMap始终都会被打包。",[11,862,863],{},"各个地图打包之后除了DefaultMap之外都是独立的，无论命名成什么都会被自动加载，而_P的优先级会被应用到整个Pak文件上。",[11,865,866],{},"而目前，只要在扫描目录里面的pak文件，都会被自动加载。从FPakPlatformFile::Initialize的调用中看到，除了在SHIPPING模式下提供的命令行外，有如下的目录会被添加到扫描中：",[629,868,869,872,875],{},[11,870,871],{},"OutPakFolders.Add(FString::Printf(TEXT(\"%sPaks\u002F\"), *FPaths::GameContentDir()));",[11,873,874],{},"OutPakFolders.Add(FString::Printf(TEXT(\"%sPaks\u002F\"), *FPaths::GameSavedDir()));",[11,876,877],{},"OutPakFolders.Add(FString::Printf(TEXT(\"%sPaks\u002F\"), *FPaths::EngineContentDir()));",[11,879,880],{},"也就是说，可以在一开始打包时，只选中Default地图，之后其他的地图可以每个或每组地图单独选中，然后打包成Patch改成地图名称的形式添加到游戏中去。",[11,882,883],{},"这样一来，就可以很方便的添加后续的地图内容到发布版本中去了，只是多少还需要维护一个文件列表。",[11,885,886],{},"总体而言，使用一个空地图作为程序本体，将通用的逻辑、材质放到其中，其他地图大致保持各自独立。这样每次就可以对对应的模块进行更新替换就行了。",[176,888,890],{"id":889},"chunks","Chunks",[11,892,893],{},"Chunks这个功能可以在编辑器中指定内容将会被打包到哪一个Patch中去，但是这个功能必须在编辑器设定中Genera>Experiment中打开：",[11,895,896],{},[679,897],{"alt":898,"src":899},"msohtmlclipclip_image001","\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fmsohtmlclipclip_image001_thumb.png",[11,901,902],{},"打开选项之后，就可以在内容管理器的右键菜单中看到：",[11,904,905],{},[679,906],{"alt":681,"src":907},"\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fimage_thumb-6.png",[11,909,910],{},"Chunk相关的选项了。对不同的资源设定了Chunk之后就可以在打包时选中这个功能，打包之后就会有不同的分包了。",[11,912,913],{},[679,914],{"alt":681,"src":915},"\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fimage_thumb-7.png",[11,917,918],{},"Chunk功能必须在Pak开启时使用，因为不Pak打包的话，原本的项目文件就已经足够Chunk了。",[11,920,921],{},"另外，如果要使用Http chunk install功能的话，不要将Default Map放到Chunk中去，否则由于本地不会保留那些Chunk，程序将会由于没有Default Map而无法开启。",[11,923,924],{},"如果对在Patch的时候也使用chunk的话，将会对不同的chunk包生成Patch，但是即使该chunk包的内容没有改变，也依然会生成一个1K的Dammy。",[11,926,927],{},"不过这其中最大的限制是，对chunk进行patch时只能针对Release时在AssetRegistry注册过的地图，如果添加没有注册过的地图则无法打包成功。",[629,929,930],{},[11,931,932],{},"Program.Main: ERROR: AutomationTool terminated with exception: AutomationTool.CommandUtils+CommandFailedException: Command failed (Result:3): D:\\Code\\UE_4.16\\Engine\\Binaries\\Win64\\UnrealPak.exe F:\\Ue_Patch\\1.0_nomap\\WindowsNoEditor\\P001\\Content\\Paks\\P001-WindowsNoEditor_P.pak -create=\"C:\\Users\\Admin\\AppData\\Roaming\\Unreal Engine\\AutomationTool\\Logs\\D+Code+UE_4.16\\PakList_P001-WindowsNoEditor_P.txt\" -encryptionini -enginedir=\"D:\\Code\\UE_4.16\\Engine\" -projectdir=\"E:\\Ues\\P001\" -platform=Windows -abslog=\"C:\\Users\\Admin\\AppData\\Roaming\\Unreal Engine\\AutomationTool\\Logs\\D+Code+UE_4.16\\PakLog_P001-WindowsNoEditor_P.log\" -installed -order=E:\\Ues\\P001\\Build\\WindowsNoEditor\\FileOpenOrder\\CookerOpenOrder.log -UTF8Output -generatepatch=E:\\Ues\\P001\\Releases\\5.0\\WindowsNoEditor\\P001-WindowsNoEditor.pak -tempfiles=D:\\Code\\UE_4.16\\TempFiles -multiprocess -patchpaddingalign=2048. See logfile for details: 'UnrealPak-2017.05.07-11.00.34.txt'",[11,934,935],{},"会出现类似这样的错误。",[176,937,939],{"id":938},"http-chunks-install","Http Chunks Install",[11,941,942],{},"在使用Chunk的基础上，还可以进一步的使用Http Chunks Install功能，这个功能通常被用于移动端的碎片化安装功能",[11,944,945],{},[679,946],{"alt":947,"src":948},"msohtmlclipclip_image0016","\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fmsohtmlclipclip_image0016_thumb.png",[11,950,951],{},"不过在桌面端也可以进行使用。其实实现中并没有支持断点续传，不过事实上，分块下载本身就在一定程度上充当了断点续传的功能。",[11,953,954],{},"使用起来也比较简单，直接勾选并填写输出路径、版本名称之类的就可以了。",[11,956,957],{},[679,958],{"alt":681,"src":959},"\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fimage_thumb-13.png",[11,961,962],{},"不过要注意确保其输出目录是确实存在的，和其他的目录设定不同，这里是会进行写入检测的。",[11,964,965],{},"另外，在Chunk Version Name里面输入之后记得敲回车才能确认输入。这里的输入框功能有点奇怪，直接填完之后点别的地方不会确认输入，会直接清空。",[11,967,968],{},"如果是移动端打包的话，无需添加配置文件，直接点击添加按钮盘的下拉三角即可：",[11,970,971],{},[679,972],{"alt":973,"src":974},"msohtmlclipclip_image0011","\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fmsohtmlclipclip_image0011_thumb.png",[11,976,977],{},"打包成功的话会在输出目录输出类似这样的内容：",[11,979,980],{},[679,981],{"alt":681,"src":982},"\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fimage_thumb-8.png",[11,984,985],{},"Http chunk install的功能也是以一个chunk为目标进行的，只是每个chunk会被分成更小的块，方便传输。也就是说，要使用这个功能，还是需要有一个自行设计的文件列表来进行版本管理的。",[11,987,988],{},"要进行碎片安装，直接对某个文件执行Request Content：",[11,990,991],{},[679,992],{"alt":681,"src":993},"\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fimage_thumb-9.png",[11,995,996],{},"这里面填写的URL对应的就是上面生成的那些内容，需要自行部署到服务器上。",[11,998,999],{},"如果Success的话，会返回一个Mobile Pending Content的引用，继续",[11,1001,1002],{},[679,1003],{"alt":681,"src":1004},"\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fimage_thumb-10.png",[11,1006,1007],{},"如果成功的话就可以对这个引用进行Mount了",[11,1009,1010],{},[679,1011],{"alt":681,"src":1012},"\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fimage_thumb-11.png",[11,1014,1015],{},"这样就可以使用这个包的内容了，这套函数本身会检测本地有没有pak文件，有的话就不会进行下载了。",[11,1017,1018],{},"上面填写的Install Directory在windows下是以项目内容目录下的PersistentDownloadDir为基础的，所以填写..\u002FContent\u002FPaks\u002F的话就可以到达自动加载目录，下次游戏运行时包中的内容就可以直接使用了。不过要留意的是，如果被自动加载了的话，那个文件就无法被直接替换掉了。",[23,1020,1021],{"id":1021},"总结",[11,1023,1024],{},"由于UE4并未提供Release版本之间的增量功能，要实现地图下载之类的功能的话，可以使用DLC或者分Map打包。如果现在的打包实在无法满足需求的话，也可以选择自己用UnreakPack进行打包并自行进行Mount管理来进行补正，有这个需求的话可能会遇到FPackageName::RegisterMountPoint之类函数的使用。不过相信打包功能在之后的版本更新中会有所增强，如果不是有紧急的打包需求的话，不建议跳这个坑。",{"title":39,"searchDepth":53,"depth":59,"links":1026},[1027,1028,1034,1039],{"id":571,"depth":53,"text":572},{"id":672,"depth":53,"text":672,"children":1029},[1030,1031,1032,1033],{"id":685,"depth":59,"text":686},{"id":714,"depth":59,"text":715},{"id":741,"depth":59,"text":742},{"id":797,"depth":59,"text":798},{"id":844,"depth":53,"text":844,"children":1035},[1036,1037,1038],{"id":853,"depth":59,"text":854},{"id":889,"depth":59,"text":890},{"id":938,"depth":59,"text":939},{"id":1021,"depth":53,"text":1021},"2017-05-01",{"layout":535,"status":536,"published":104,"author":1042,"author_login":539,"author_email":540,"wordpress_id":1043,"wordpress_url":1044,"date_gmt":1045,"excerpt":1046},{"display_name":538,"login":539,"email":540,"url":39},1646,"\u002F\u002F?p=1646","2017-05-01 10:32:13 +0000",{"type":8,"value":1047},[1048],[11,1049,562],{},"\u002F2017-05-01-ue4-patch-release-dlc",{"title":557,"description":562},"_legacy\u002F2017\u002F2017-05-01-ue4-patch-release-dlc",[552,1054,1055,1056,26],"Release","Patch","DLC","cJwigzRyewKqORjMzLTqhnQa8zSJWqk_pxLSbY0jaAA",1788763180011]