ISSUE-001
虚幻引擎文件路径错误
常见表现
加载进度卡住,加载过程中提示存储空间不足,加载过程中提示文件操作失败,加载过程中闪退。
相关源码
虚幻引擎 IOSPlatformFile.cpp
FString FIOSPlatformFile::ConvertToPlatformPath(const FString& Filename, bool bForWrite, bool bIsPublicWrite)
{
FString Result = Filename;
if (Result.StartsWith(TEXT("/var/")))
{
return Result;
}
if (bForWrite)
{
static FString PublicWritePathBase = FString([NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]) + TEXT("/");
static FString PrivateWritePathBase = FString([NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]) + TEXT("/");
return (bIsPublicWrite ? PublicWritePathBase : PrivateWritePathBase) + Result;
}
else
{
static FString ReadPathBase = FString([[NSBundle mainBundle] bundlePath]) + TEXT("/cookeddata/");
return ReadPathBase + Result.ToLower();
}
}
原因分析
源码中根据字符串是否以/var/开头,来判断字符串是否绝对路径。如果不是绝对路径,则会在字符串前面附加Documents目录或Library目录的路径。
在iOS上,绝对路径的格式为:/var/mobile/Containers/Data/Application/{GUID}/Documents
在macOS上,绝对路径的格式为:/Users/$USER/Containers/{BUNDLE_ID}/Data/Documents
macOS的绝对路径并不是以/var/开头的,调用ConvertToPlatformPath()后会生成错误的路径。
如果所有文件操作都经过一遍ConvertToPlatformPath()处理的话,是不会导致报错的,只有在读写操作不一致时才会出问题。
例如通过虚幻引擎API写入文件,间接调用了ConvertToPlatformPath(),但在读取文件时却直接使用NSFileManager之类的系统API,就会出现路径不一致,从而导致各种报错。
解决方法
文件链接法
BUNDLE_ID=com.companyname.appname
rm -r /Users/$USER/Library/Containers/$BUNDLE_ID/Data/Documents/Users/$USER/Library/Containers/$BUNDLE_ID/Data && ln -sf /Users/$USER/Library/Containers/$BUNDLE_ID/Data /Users/$USER/Library/Containers/$BUNDLE_ID/Data/Documents/Users/$USER/Library/Containers/$BUNDLE_ID/Data
rm -r /Users/$USER/Library/Containers/$BUNDLE_ID/Data/Library/Users/$USER/Library/Containers/$BUNDLE_ID/Data && ln -sf /Users/$USER/Library/Containers/$BUNDLE_ID/Data /Users/$USER/Library/Containers/$BUNDLE_ID/Data/Library/Users/$USER/Library/Containers/$BUNDLE_ID/Data
补丁法
修改常量字符串/var/为/User(保持字符串长度不变)
BUNDLE_ID=com.companyname.appname
BUNDLE_PATH=~/Library/Containers/io.playcover.PlayCover/Applications/$BUNDLE_ID.app
EXECUTABLE=$BUNDLE_PATH/$(/usr/libexec/PlistBuddy -c "Print :CFBundleExecutable" $BUNDLE_PATH/Info.plist)
perl -e 'open F, "+<:raw", $ARGV[0] or die $!; local $/; my $d = <F>; my $i = index($d, "\x2F\x00\x76\x00\x61\x00\x72\x00\x2F\x00\x00\x00"); exit 1 if $i < 0; seek F, $i, 0; print F "\x2F\x00\x55\x00\x73\x00\x65\x00\x72\x00\x00\x00"; close F;' $EXECUTABLE
codesign -fs- $EXECUTABLE --deep --preserve-metadata=entitlements