草庐IT

【UE】pak的mount(带源码解析)

峰度偏偏 2023-07-23 原文

本文使用的引擎版本为UE4.27
为了方便理解,文中选取的代码均为部分截取,只截取与小节相关的部分

文章目录

概述

正常的散文件加载是使用FFileHelper::LoadFileToArray等接口来读取文件内容。但pak作为一个类似于压缩包的格式,其中的文件无法直接使用这种方式读取。故需要使用mount来挂载。

mount操作告诉系统有哪些文件可以从pak中读到,并提供虚拟路径使系统可以通过FPakPlatformFile::CopyFileFFileHelper::LoadFileToString等操作普通文件的方法操作pak中的文件

几个涉及到的结构

IPlatformFile:文件IO的接口,是整个文件系统的基类。该类及其子类以链式组织,每个实例存有其下层的引用,每层访问的时候都先查找自身,找不到才向下层查找。

FPakPlatformFile:继承于IPlatformFile,负责pak的mount。它的私有成员ExcludedNonPakExtensions记录了几个只应存在pak里的文件拓展:uasset、umap、ubulk、uexp、uptnl和ushaderbytecode。这些文件如果在pak里没有找到,就不再查找它的下层了。

FPakListEntry:定义在FPakPlatformFile内的结构体,有ReadOrder和PakFile两个属性。FPakPlatformFile中用几个TArray来记录mount到的pak

FPakFile:Pak在C++中存储的形式。有PakFilename、LastUseTime、Info等成员
FPakInfo:Pak的提纲信息,存有版本号、哈希值、魔数、是否加密等
FPakEntry:Pak中单个文件的信息,存储文件大小、在pak中的偏移量、是否加密等

Mount时机

很多地方都会调到FPakPlatformFile::Mount进行pak的Mount,这里只分析引擎启动时进行的mount

引擎启动时进行的初始化mount位于 FPakPlatformFile::Initialize。如果使用命令行指定了PakFile单例,会在引擎PreInit的阶段通过ConditionallyCreateFileWrapper函数创建单例并调用 FPakPlatformFile::Initialize。如果没有在命令行中指定,则该初始化函数在EditorInit阶段才进行。后者的调用堆栈如下:

pak读取优先级

概述中提到,mount的作用是为了提供一种读取pak文件的方法,使pak中的文件也能使用操作散文件的操作方法。即:mount并不是读取pak中的文件。故mount顺序其实对pak的读取优先级没有影响

那么pak读取的优先级到底取决于什么呢? 做热更需求的时候如何保证自己新添加的pak可以覆盖掉原来pak中的资源?答案是取决于FPakListEntry的ReadOrder:ReadOrder大的pak会覆盖小的pak的资源内容

// IPlatformFilePak.h
struct FPakListEntry
{
	FPakListEntry(): ReadOrder(0), PakFile(nullptr){}

	uint32		ReadOrder;
	TRefCountPtr<FPakFile>	PakFile;
	
	// ReadOrder越大,优先级越高。故热更的时候只需要保证新pak的ReadOrder大于原来的pak即可
	FORCEINLINE bool operator < (const FPakListEntry& RHS) const
	{
		return ReadOrder > RHS.ReadOrder;
	}
};	

这个ReadOrder是在FPakPlatformFile::Mount(const TCHAR* InPakFilename, uint32 PakOrder, ...)指定的,未指定的话使用最低的优先级0

在进入mount之前,Initialize函数中会按照pak的路径和名字,给这个pak赋一个ReadOrder(即下文中的PakOrder)。

目录优先级

// IPlatformFilePak.cpp
bool FPakPlatformFile::Initialize(IPlatformFile* Inner, const TCHAR* CmdLine)
{
	……
#if EXCLUDE_NONPAK_UE_EXTENSIONS && !WITH_EDITOR
	// Extensions for file types that should only ever be in a pak file. Used to stop unnecessary access to the lower level platform file
	ExcludedNonPakExtensions.Add(TEXT("uasset"));
	ExcludedNonPakExtensions.Add(TEXT("umap"));
	ExcludedNonPakExtensions.Add(TEXT("ubulk"));
	ExcludedNonPakExtensions.Add(TEXT("uexp"));
	ExcludedNonPakExtensions.Add(TEXT("uptnl"));
	ExcludedNonPakExtensions.Add(TEXT("ushaderbytecode"));
#endif
	……
	// Find and mount pak files from the specified directories.
	TArray<FString> PakFolders;
	GetPakFolders(FCommandLine::Get(), PakFolders);
	MountAllPakFiles(PakFolders, *StartupPaksWildcard);
	……
}

FPakPlatformFile初始化的时候除了对一些成员变量进行初始化以外,还会调用MountAllPakFiles进行pak的mount。

int32 FPakPlatformFile::MountAllPakFiles(const TArray<FString>& PakFolders, const FString& WildCard)
{
	……
	if (bMountPaks)
	{
		TArray<FString> FoundPakFiles;
		FindAllPakFiles(LowerLevel, PakFolders, WildCard, FoundPakFiles);
		……
		for (int32 PakFileIndex = 0; PakFileIndex < FoundPakFiles.Num(); PakFileIndex++)
		{
			const FString& PakFilename = FoundPakFiles[PakFileIndex];
			……
			uint32 PakOrder = GetPakOrderFromPakFilePath(PakFilename);

			UE_LOG(LogPakFile, Display, TEXT("Mounting pak file %s."), *PakFilename);

			if (Mount(*PakFilename, PakOrder))
			{
				++NumPakFilesMounted;
			}
		}
	}
	return NumPakFilesMounted;
}

MountAllPakFiles其实就是读取需要mount的pak,遍历进行挂载。
其中GetPakOrderFromPakFilePath依据pak所属目录获取其优先级:

int32 FPakPlatformFile::GetPakOrderFromPakFilePath(const FString& PakFilePath)
{
	if (PakFilePath.StartsWith(FString::Printf(TEXT("%sPaks/%s-"), *FPaths::ProjectContentDir(), FApp::GetProjectName())))
	{
		return 4;
	}
	else if (PakFilePath.StartsWith(FPaths::ProjectContentDir()))
	{
		return 3;
	}
	else if (PakFilePath.StartsWith(FPaths::EngineContentDir()))
	{
		return 2;
	}
	else if (PakFilePath.StartsWith(FPaths::ProjectSavedDir()))
	{
		return 1;
	}

	return 0;
}

可以看到,确立pak读取优先级的基本策略是:Project/Content/Paks/ProjectName-*.pak > Project/Content/Paks/*.pak > Engine/Content/Paks/*.pak > Project/Saved/Paks/*.pak

根据文件名定优先级

在经过上述目录优先级的处理以后,如果文件名以_n_P结尾,则将其优先级提升到 P a k O r d e r + ( n + 1 ) × 100     ( n ≥ 1 ) PakOrder + (n + 1)×100 \space\space\space(n \geq 1) PakOrder+(n+1)×100   (n1)

// IPlatformFilePak.cpp
bool FPakPlatformFile::Mount(const TCHAR* InPakFilename, uint32 PakOrder, const TCHAR* InPath /*= NULL*/, bool bLoadIndex /*= true*/)
{
	……
	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;
	}
	……
}

综上所述

pak优先级的确立方法:

  1. 确立pak读取优先级的基本策略是:Project/Content/Paks/ProjectName-*.pak > Project/Content/Paks/*.pak > Engine/Content/Paks/*.pak > Project/Saved/Paks/*.pak

  2. 在经过上述目录优先级的粗略处理以后,如果文件名以_n_P结尾,则将其优先级提升到 P a k O r d e r + ( n + 1 ) × 100     ( n ≥ 1 ) PakOrder + (n + 1)×100 \space\space\space(n \geq 1) PakOrder+(n+1)×100   (n1)

有关【UE】pak的mount(带源码解析)的更多相关文章

  1. Ruby 解析字符串 - 2

    我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - 用逗号、双引号和编码解析 csv - 2

    我正在使用ruby​​1.9解析以下带有MacRoman字符的csv文件#encoding:ISO-8859-1#csv_parse.csvName,main-dialogue"Marceu","Giveittohimóhe,hiswife."我做了以下解析。require'csv'input_string=File.read("../csv_parse.rb").force_encoding("ISO-8859-1").encode("UTF-8")#=>"Name,main-dialogue\r\n\"Marceu\",\"Giveittohim\x97he,hiswife.\"\

  4. ruby-on-rails - 我更新了 ruby​​ gems,现在到处都收到解析树错误和弃用警告! - 2

    简而言之错误:NOTE:Gem::SourceIndex#add_specisdeprecated,useSpecification.add_spec.Itwillberemovedonorafter2011-11-01.Gem::SourceIndex#add_speccalledfrom/opt/local/lib/ruby/site_ruby/1.8/rubygems/source_index.rb:91./opt/local/lib/ruby/gems/1.8/gems/rails-2.3.8/lib/rails/gem_dependency.rb:275:in`==':und

  5. UE4 源码阅读:从引擎启动到Receive Begin Play - 2

    一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame

  6. ruby - 用 YAML.load 解析 json 安全吗? - 2

    我正在使用ruby2.1.0我有一个json文件。例如:test.json{"item":[{"apple":1},{"banana":2}]}用YAML.load加载这个文件安全吗?YAML.load(File.read('test.json'))我正在尝试加载一个json或yaml格式的文件。 最佳答案 YAML可以加载JSONYAML.load('{"something":"test","other":4}')=>{"something"=>"test","other"=>4}JSON将无法加载YAML。JSON.load("

  7. ruby - 如何使用 Nokogiri 解析纯 HTML 表格? - 2

    我想用Nokogiri解析HTML页面。页面的一部分有一个表,它没有使用任何特定的ID。是否可以提取如下内容:Today,3,455,34Today,1,1300,3664Today,10,100000,3444,Yesterday,3454,5656,3Yesterday,3545,1000,10Yesterday,3411,36223,15来自这个HTML:TodayYesterdayQntySizeLengthLengthSizeQnty345534345456563113003664354510001010100000344434113622315

  8. python - 帮我找到合适的 ruby​​/python 解析器生成器 - 2

    我使用的第一个解析器生成器是Parse::RecDescent,它的指南/教程很棒,但它最有用的功能是它的调试工具,特别是tracing功能(通过将$RD_TRACE设置为1来激活)。我正在寻找可以帮助您调试其规则的解析器生成器。问题是,它必须用python或ruby​​编写,并且具有详细模式/跟踪模式或非常有用的调试技术。有人知道这样的解析器生成器吗?编辑:当我说调试时,我并不是指调试python或ruby​​。我指的是调试解析器生成器,查看它在每一步都在做什么,查看它正在读取的每个字符,它试图匹配的规则。希望你明白这一点。赏金编辑:要赢得赏金,请展示一个解析器生成器框架,并说明它的

  9. ruby - 如何用 Nokogiri 解析连续的标签? - 2

    我有这样的HTML代码:Label1Value1Label2Value2...我的代码不起作用。doc.css("first").eachdo|item|label=item.css("dt")value=item.css("dd")end显示所有首先标记,然后标记标签,我需要“标签:值” 最佳答案 首先,您的HTML应该有和中的元素:Label1Value1Label2Value2...但这不会改变您解析它的方式。你想找到s并遍历它们,然后在每个你可以使用next_element得到;像这样:doc=Nokogiri::HTML(

  10. ruby-on-rails - 如何在 Rails 3 中禁用 XML 解析 - 2

    我想禁用HTTP参数的自动XML解析。但我发现命令仅适用于Rails2.x,它们都不适用于3.0:config.action_controller.param_parsers.deleteMime::XML(application.rb)ActionController::Base.param_parsers.deleteMime::XMLRails3.0中的等价物是什么? 最佳答案 根据CVE-2013-0156的最新安全公告你可以将它用于Rails3.0。3.1和3.2ActionDispatch::ParamsParser::

随机推荐