IOS 基础包拆分

在前面提到了 UE 为 Android 提供了打包到 obb 中的文件过滤规则:

1
2
3
# Config/DefaultEngine.ini
[/Script/AndroidRuntimeSettings.AndroidRuntimeSettings]
+ObbFilters=-pakchunk1-*

但是 UE 并没有为 IOS 提供相应的操作,默认情况下会把 IOS 的所有的 pak 文件都打包至 IPA 中。

为了统一 Android 和 IOS 的基础包规则,我自己实现了 IOS 上类似 Android 那种指定过滤规则的功能,做个简单的介绍。

我使用的是 Mac 远程打包,流程是在 Mac 上编译代码生成 IPA,拉回 Win,在 Win 上进行 Cook,生成 Pak 文件,最后把原始 IPA 解包,再添加 Pak 等文件组合成最终 IPA。

我的需求是,自定义指定过滤规则,可以把某些文件忽略,不打包到 IPA 中。那么这一步的操作其实就位于把 IPA 解包再打包的流程里,经过翻阅 UE 的代码,发现这个操作是通过 iPhonePackager 这个独立程序来实现的,那么就需要对这个程序的代码进行改造了。

经过调试分析,发现真正实现重新打包 IPA 的操作是在以下函数中执行的:

Programs/IOS/iPhonePackager/CookTime.cs
1
2
3
4
/** 
* Using the stub IPA previously compiled on the Mac, create a new IPA with assets
*/
static public void RepackageIPAFromStub();

该函数位于 iPhonePackager-CookTime 类中。

1
2
3
4
5
6
7
8
9
10
11
12
static public void RepackageIPAFromStub()
{
// ...
string SourceDir = Path.GetFullPath(ZipSourceDir);
string[] PayloadFiles = Directory.GetFiles(SourceDir, "*.*", Config.bIterate ? SearchOption.TopDirectoryOnly : SearchOption.AllDirectories);
foreach (string Filename in PayloadFiles)
{
// read file to memory,add to ZipFileSystem
// generate stub and ipa
}
//...
}

需要做的操作就是介入这个过程,把 PayloadFiles 中的文件列表通过我们自定义的规则来执行过滤。

从流程上分为以下几个步骤:

  1. 从项目中读取 Filter 的配置
  2. 创建出真正的过滤器
  3. RepackageIPAFromStub 遍历文件的流程里使用过滤器进行检测是否需要被打入 ipa

只需要几十行代码就可以实现,首先需要添加一个 IniReader 的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using Tools.DotNETCommon;
using System.Runtime.InteropServices;
using Ini;

namespace Ini
{
public class IniReader
{
private string path;

[DllImport("kernel32")]
private static extern int GetPrivateProfileString(string section, string key, string def,
StringBuilder retVal, int size, string filePath);

public IniReader(string INIPath)
{
path = INIPath;
}

public string ReadValue(string Section, string Key)
{
StringBuilder ReaderBuffer = new StringBuilder(255);
int ret = GetPrivateProfileString(Section, Key, "", ReaderBuffer, 255, this.path);
return ReaderBuffer.ToString();
}
}
}

然后在 RepackageIPAFromStub 函数中创建过滤器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
FileFilter IpaPakFileFilter = new FileFilter(FileFilterType.Include);
{
string ProjectDir = Directory.GetParent(Path.GetFullPath(Config.ProjectFile)).FullName;
// Program.Log("ProjectDir path {0}", ProjectDir);
string EngineIni = Path.Combine(ProjectDir,"Config","DefaultEngine.ini");
// Program.Log("EngineIni path {0}", EngineIni);
IniReader EngineIniReader = new IniReader(EngineIni);
// string RawPakFilterRules = EngineIniReader.ReadValue("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "IPAFilters");
Program.Log("RawPakFilterRules {0}", RawPakFilterRules);
string[] PakRules = RawPakFilterRules.Split(',');
// foreach(string Rule in PakRules) {Program.Log("PakRules {0}", Rule);}

List<string> PakFilters = new List<string>(PakRules);
if (PakFilters != null)
{
IpaPakFileFilter.AddRules(PakFilters);
}
}

这里从项目的 Config/DefaultEngine.ini 的[/Script/IOSRuntimeSettings.IOSRuntimeSettings]项读取 IPAFilters 的值,规则与 Android 相同,但是要把规则都写在一行,多个规则以逗号分隔。

1
2
[/Script/IOSRuntimeSettings.IOSRuntimeSettings]
IPAFilters=-*.pak,pakchunk0-*

最终,还需要在 RepackageIPAFromStub 遍历 Payload 文件的循环中进行检测是否匹配我们指定的过滤规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static public void RepackageIPAFromStub()
{
// ...
string SourceDir = Path.GetFullPath(ZipSourceDir);
string[] PayloadFiles = Directory.GetFiles(SourceDir, "*.*", Config.bIterate ? SearchOption.TopDirectoryOnly : SearchOption.AllDirectories);
foreach (string Filename in PayloadFiles)
{
if (!IpaPakFileFilter.Matches(Filename))
{
Program.Log("IpaPakFileFilter not match file {0}", Filename);
continue;
}
// Program.Log("IpaPakFileFilter match file {0}", Filename);
}
//...
}

这样再执行打包 IOS,就会按照指定的过滤规则来添加文件了,实现了与 Android 上一致的行为。

打包过程中的 Log 如下(上文代码已注释):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Saving IPA ...
ProjectDir path C:\BuildAgent\workspace\PackageWindows\Client
EngineIni path C:\BuildAgent\workspace\PackageWindows\Client\Config\DefaultEngine.ini
RawPakFilterRules -*.pak,pakchunk0-*
PakRules -*.pak
PakRules pakchunk0-*
IpaPakFileFilter match file C:\BuildAgent\workspace\PackageWindows\Client\Saved\StagedBuilds\IOS\Assets.car
IpaPakFileFilter match file C:\BuildAgent\workspace\PackageWindows\Client\Saved\StagedBuilds\IOS\Info.plist
IpaPakFileFilter match file C:\BuildAgent\workspace\PackageWindows\Client\Saved\StagedBuilds\IOS\LaunchScreenIOS.webp
IpaPakFileFilter match file C:\BuildAgent\workspace\PackageWindows\Client\Saved\StagedBuilds\IOS\Manifest_DebugFiles_IOS.txt
IpaPakFileFilter match file C:\BuildAgent\workspace\PackageWindows\Client\Saved\StagedBuilds\IOS\Manifest_NonUFSFiles_IOS.txt
IpaPakFileFilter match file C:\BuildAgent\workspace\PackageWindows\Client\Saved\StagedBuilds\IOS\mute.caf
IpaPakFileFilter match file C:\BuildAgent\workspace\PackageWindows\Client\Saved\StagedBuilds\IOS\ue4commandline.txt
IpaPakFileFilter match file C:\BuildAgent\workspace\PackageWindows\Client\Saved\StagedBuilds\IOS\cookeddata\fgame\content\movies\logo.mp4
IpaPakFileFilter match file C:\BuildAgent\workspace\PackageWindows\Client\Saved\StagedBuilds\IOS\cookeddata\fgame\content\movies\sparkmore.mp4
IpaPakFileFilter match file C:\BuildAgent\workspace\PackageWindows\Client\Saved\StagedBuilds\IOS\cookeddata\fgame\content\paks\pakchunk0-ios.pak
IpaPakFileFilter not match file C:\BuildAgent\workspace\PackageWindows\Client\Saved\StagedBuilds\IOS\cookeddata\fgame\content\paks\pakchunk1-ios.pak
IpaPakFileFilter not match file C:\BuildAgent\workspace\PackageWindows\Client\Saved\StagedBuilds\IOS\cookeddata\fgame\content\paks\pakchunk2-ios.pak
IpaPakFileFilter match file C:\BuildAgent\workspace\PackageWindows\Client\Saved\StagedBuilds\IOS\Engine\Content\SlateDebug\Fonts\LastResort.ttf
IpaPakFileFilter match file C:\BuildAgent\workspace\PackageWindows\Client\Saved\StagedBuilds\IOS\GCloudVoice.bundle\files\config.json
IpaPakFileFilter match file C:\BuildAgent\workspace\PackageWindows\Client\Saved\StagedBuilds\IOS\GCloudVoice.bundle\files\libwxvoiceembed.bin
IpaPakFileFilter match file C:\BuildAgent\workspace\PackageWindows\Client\Saved\StagedBuilds\IOS\GCloudVoice.bundle\files\mute_detection.aiff
IpaPakFileFilter match file C:\BuildAgent\workspace\PackageWindows\Client\Saved\StagedBuilds\IOS\GRobotResource.bundle\config.json
...

可以看到,过滤规则已经生效了。