发布Unity项目到macOS AppStore和Steam
这篇blog记录下如何将Unity导出的macOS应用提交到Steam和AppStore。通常情况下我们在macOS系统上的开发依赖于Xcode,如果在Unity的PlayerSettings
里面没有设置签名和BundleId,那么只能使用相应的命令行工具对二进制的Game.app
进行签名和处理。
首先会介绍一些签名相关的基本概念和命令,或者你可以直接跳到:
提交到Steam和第三方直接发布
提交到macOS-App-Store
包体配置和签名
基本概念
Apple提供了签名工具用于给应用文件进行签名,通常macOS系统的应用都是一个.app
的Bundle,不像Windows系统中是一个.exe
的文件。所以macOS的codesign 是对于整个bundle的所有文件的。
Designated Requirement(DR)
Apple会给每一个要签名的应用创建一个Designated Requirement(DR),这个DR通常由应用的BundleID进行标识。
Resource envelope
签名会对Game.app下的所有代码和非代码的资源文件以及第三方的插件进行签名,计算出每个文件的hash,同时再合并所有资源文件的hash生成最终的一个Hash值。
Resource envelope 有两个版本,最新的 v2版本会对所有文件进行签名,OSX Mavericks 10.9之后都是使用 v2的版本
只有有任何记录的文件有改动,都会通过hash校验来被检测出来。所以即使没有修改Game.app下的文件,在签名完成之后添加新的文件,整个Budnle也是校验不过的。无法通过GateKeeper
的验证。
应用Bundle处理
修改BundleId
直接打开Game.app/Contents/Info.plist
修改CFBundleIdentifier
。
移除不需要的文件
使用Unity编辑器时,如果导入macOS或者iOS的framewor
和bundle
文件,Unity会自动对文件夹下的所有文件和文件夹创建一个对应的.meta
文件。
这个文件存在的路径在Bundle中不是标准的路径,同时会导致codesign
时失败,我们需要移除它。
1 | #remove all remove meta files |
CodeSign签名
第三方库签名
Apple要求所有提交给macOS AppStore和经Notarization认证后直接分发的应用中,所有的第三方库dylib,framework,bundle
都需要被签名。
同时需要注意:提交到acOS AppStore的应用中,所有的第三方库和插件都需要使用Apple开发者的签名。
也就是说你如果提交给macOS Store的话,还是需要给类似libssl.dylib
这类的库重新签名。
Entitlements文件
在使用CodeSign签名之前我们需要创建.entitlements
文件。如果使用xcode开发应用,xcode会帮我们创建好。但我们只有一个.app
的Bundle文件,那么只能手动创建然后,使用命令行进行签名。
macOS AppStore需要的.entitlements
文件和第三方发布提交给Steam的.entitlements
文件是不同的.
下面是一个最简的配置,如果应用有其他的capabilities配置,需要再额外配置其他的key/value。
For macOS AppStore
1
2
3
4
5
6
7
8
9
10
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist
For Steam
1 |
|
CodeSign 命令
codesign命令
1 | codesign --deep --force --verify --verbose=4 --timestamp --options runtime --entitlements $EntitlementsFile --sign "$CertName" $FileName |
参数sign填写的证书的名称 类似于Mac Developer:XXXXX(TEAMID)
之类的。这个参数会有自动匹配只要不出现冲突就可以。
如何验证codesign是否正确
1 | codesign --verify --deep --strict --verbose=4 Game.app |
验证通过则会输出
1 | Game.app: valid on disk |
提交到Steam和第三方直接发布
提交到Steam直接分发给用户的macOS程序需要通过Apple的认证流程,详细的说明 Notarizing macOS Software Before Distribution
证书创建
首先是证书创建,需要在Apple Developer后台的证书中创建一个 Developer ID Application
证书。
Developer ID Application Certificate
只能由Apple Developer的account holder创建,开发者账号下即使是有admin权限的子账号也是无法创建的。
和创建普通的开发者证书一样,提交CSR文件,创建完成后下载双击导入到keychain中。
包体配置和准备
现在我们有一个Game.app
的二进制包,以及一个Developer ID Application Certificate
。
按照第一部分的应用Bundle处理中的说明
进行下面的操作
- 删除
Game.app
下面的所有.meta文件 - 在
Game.app
同级目录创建Game.entitlements
- 修改
Game.app/Contents/Info.plist
中的信息
修复SteamLib
通常Unity使用的Steamworks Lib是第三方开发的rlabrecque/Steamworks.NET。在codesign之前我们要对他进行处理。
rlabrecque/Steamworks.NET
的库文件为Steam_api.bundle
- Steam_api.bundle的 CFBundleIdentifier为
com.rileylabrecque.steam_api
,不是标准的BundleId,我们需要将它改成com.rileylabrecque.steamapi
去掉下划线。 修改的文件地址在steam_api.bundle/Contents/Info.plist
中。 libsteam_api.dylib
包含32位的symbol。提交到Apple的bundle必须支持64位的文件,32位的symbol需要删除。lipo -remove x86_64 libsteam_api.dylib -o libsteam_api.dylib
这样我们对SteamworkLibrary的处理就完成了。如果有其他的第三方库有类似的问题,也需要进行相同的操作。
Codesign签名
Apple文档上建议首先要先对所有的lib,framework以及bundle进行签名,最后再对Game.app
进行签名。使用--deep
参数进行签名不保证能够正确处理嵌套的lib的正确签名。只能用于文件修改后的codesign patch。
- 对所有的library签名
$BINARY = ‘Game.app’
1 | libfile=`find ./$BINARY -name '*.dylib'` |
- 在所有的lib签名成功后对整体的包进行签名
1 | codesign --deep --force --verify --verbose --timestamp --options runtime --entitlements $ENTITLEMENTS --sign "$SIGNCERT" $BINARY -i $BUNDLE_ID |
需要注意的是--sign
参数输入”Developer ID Application:”就可以了,codesign会从keychain中自动匹配
提交苹果Notarization验证
1.提交到Notarization需要将Game.app
打包为一个zip包。
需要注意的是我们要使用apple提供的ditto命令进行打包,直接使用zip
命令会丢失签名信息其他额外信息
1 | ditto -c -k --sequesterRsrc --keepParent "Game.app" "Game.app.zip" |
2.提交认证请求
1 | xcrun altool --notarize-app --primary-bundle-id "${Bundle_Identifier}" --username "${APPLE_ID_ACCOUNT}" --password "${APPLE_ID_PWD}" --file Game.app.zip --asc-provider "${ASC_PROVIDER}" |
asc-provider
是ProviderShortname
, 通常是开发者证书末尾的那个Team ID
,在Apple Developer/Membership/Team ID
中可以找到
比较老的apple developer账号ProviderShortname
和Team ID
不一致,我们可以使用命令来查询ProviderShortName
。
1 | $ xcrun altool --list-providers -u "AC_USERNAME" -p "PWD" |
提交成功之后会显示一个RequestId 如1809fca2-XXXX-XXXX-XXXX-14aa62f6XXXX
。
1 | altool[16765:378423] No errors uploading 'Game.app.zip'. |
根据RequestID 我们可以查询notarization的结果,通常需要等待5分钟左右。
1 | #查询结果 |
提交成功后AppleID邮箱会收到一个来自Apple的邮件。
如果失败可以根据Request Identifier查询相应的log。
- 在提交成功之后我们就可以给应用盖章了。
1 | $ xcrun stapler staple "Game.app" |
这里注意的是不能直接给Game.app.zip
staple。
对于Staple完成的.app
Bundle,再zip之后就可以直接分发了。
提交到macOS App Store
证书创建
提交到macOS AppStore需要两个证书
- Mac App Distribution Certificate
- Mac Installer Distribution Certificate
Mac App Distribution Certificate
用于给bundle CodeSign
Mac Installer Distribution Certificate
用于pack bundle 之后提交到itunes server审核
包体配置和准备
和提交到Steam和第三方直接发布中的流程一致
- 删除
Game.app
下面的所有.meta文件 - 在
Game.app
同级目录创建Game.entitlements
- 修改
Game.app/Contents/Info.plist
中的信息 - 处理第三方lib,修改bundleId或删除symbol
- 对所有lib签名
- 对Game.app签名,这里使用
Mac App Distribution
证书进行签名
生成pkg文件
签名完成后需要将 Game.app
pack成 Game.app.pkg
文件。
1 | productbuild --component ${BINARY} /Applications --sign "3rd Party Mac Developer Installer:" "${PKG_FILE}" |
pack成功之后会得到一个Game.app.pkg
。
使用AppLoader提交到itune服务器
和iOS开发相同,使用application loader提交的itunes服务器进行验证。由于最新的xcode 11不再提供GUI版本的的 application loader。我们只能使用命令行xcrun alttol
进行处理
- 首再提交到itunes之前先验证。
1 | xcrun altool --validate-app -f Game.app.pkg -u "${USER}" -p "${PWD}" |
等到处理结果,如果提示not error
则我们可以提交应用。
需要注意的是xcrun altool --validate-app
并不会查出所有的错误,再提交到itunes之后,apple还会对包体进行验证。可能本地validation
没有错误,但是还是会被apple拒绝。
- 上传pkg到itune服务器
1 | $ xcrun altool --upload-app -f Game.app.pkg -u "${USER}" -p "${PWD}" |
提示no error uploading package
,就表示上传成功了。我们只需要等待itunes后台处理。
如果收到了Apple的邮件:App Store Connect: Your app "XXXXX" (Apple ID: 123456789 Version: 1.0 Build: 0) has one or more issues
就表示本次提交的包里面还有其他错误。
当这个版本的提交itunes服务器通过了验证,则不会收到邮件,直接在itunes connect后台可以看到提交的版本了。
在iTunes服务器验证失败后,就需要重新走一遍codesign->pkg->upload的流程。和ios开发相同,对于同一个版本每次提交必须要有不同的build,手动修改Game.app/Contents/Info.plist的build version重新操作即可。
FAQ
ITMS-90303: Unable to Sign
code object is not signed at all In subcomponent: 有额外的文件没有删除,删除文件后再签名。
ITMS-90238: Invalid Signature
某些lib文件没有签名,或者有额外的文件没有删除
codesign 时提示CSSMERR_TP_CERT_REVOKED
证书过期需要重新从apple developer后台下载证书
codesign 提示 errSecInternalComponent
需要解锁Security的keychain读取证书进行签名
Reference
Unity Manual: Delivering your application to the Mac App Store
gist:how-to-notarize-unity-for-macos.md
Notarizing macOS Software Before Distribution
Technical Note TN2206:macOS Code Signing In Depth
感谢阅读,如果有任何错误或者问题请下下面留言。