Yesterday I was fighting with the F****** codesigning in macOS because as you know Unity has abandoned this platform years ago and the support it’s really sh** and instead of generating a Xcode project it generates the *.app that you need later to:
Note: DON’T ever look at Unity’s Manual page: Unity - Manual: Build and distribute a macOS application
It’s obsolete, ancient, abandoned… Whatever you want to name it.
-
Enable Player settings checkbox: UseMacAppStoreValidation.
-
Apply a code snippet to fix a bug on retina displays:
void IEnumerator Start() {
// https://docs.unity3d.com/Manual/HOWTO-PortToAppleMacStore.html
if (Screen.fullScreen) {
//MacBook Pro Retina 15: width = 2880 , MacBook Pro Retina 13: width = 2496 ?
//could check device model name, but not sure now about retina 13 device model name
//if last resolution is almost retina resolution...
Resolution[] resolutions = Screen.resolutions;
if (resolutions.Length > 0 && resolutions[resolutions.Length - 1].width > 2048) {
Screen.fullScreen = false;
yield return null;
Screen.fullScreen = true;
yield return null;
}
}
}
Really? There is no fix by Unity that doesn’t involve this?
- Generate icons on png format with sRGB color profile and preserving the alpha channel following the table in this page: Designing for macOS | Apple Developer Documentation
If your icons are not in sRGB or don’t have alpha channel you can (on macOS terminal):
Why Unity doesn’t do this step for us???
- Use this commands to generate iconset file (*.icns):
http://stackoverflow.com/a/20703594/1149936
Why Unity doesn’t do this step for us???
- Generate a UnityPlayerIcon.png (64x64) for your app.
Why we need to do it separately and doesn’t use PlayerSettings “Default icon”?
- Make a PostProcessBuild script to:
[PostProcessBuild(1)]
public static void OnPostprocessBuild(BuildTarget target, string pathToBuiltProject) {
bool isOSX = target == BuildTarget.StandaloneOSXIntel || target == BuildTarget.StandaloneOSXIntel64 || target == BuildTarget.StandaloneOSXUniversal;
if (!isOSX) return;
string plistPath = string.Format("{0}/Contents/Info.plist", pathToBuiltProject);
PlistDocument plist = new PlistDocument();
plist.ReadFromString(File.ReadAllText(plistPath));
PlistElementDict rootDict = plist.root;
rootDict.SetString("CFBundleGetInfoString", PlayerSettings.companyName);
// Set something like this if you also have a iOS version because you can't reuse an identifier for two apps. If you don't have a iOS version you can set "PlayerSettings.bundleIdentifier" directly
rootDict.SetString("CFBundleIdentifier", string.Format("{0}.{1}", PlayerSettings.bundleIdentifier, "osx"));
rootDict.SetString("CFBundleShortVersionString", PlayerSettings.bundleVersion);
// Change this value
rootDict.SetString("CFBundleSignature", "{FOUR_LETTER_STRING}");
rootDict.SetString("CFBundleVersion", PlayerSettings.bundleVersion);
// Full table: https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/LaunchServicesKeys.html#//apple_ref/doc/uid/TP40009250-SW8
rootDict.SetString("LSApplicationCategoryType", "public.app-category.games");
PlistElementArray platformsArray = rootDict.CreateArray("CFBundleSupportedPlatforms");
platformsArray.AddString("MacOSX");
File.WriteAllText(plistPath, plist.WriteToString());
// Changes each plugin CFBundleIdentifier to your app identifier
foreach(string pluginPlistFilePath in Directory.GetFiles(string.Format("{0}/Contents/Plugins", pathToBuiltProject), "Info.plist", SearchOption.AllDirectories)) {
PlistDocument pluginPlist = new PlistDocument();
pluginPlist.ReadFromString(File.ReadAllText(pluginPlistFilePath));
PlistElementDict pluginRootDict = pluginPlist.root;
// Editor your value
pluginRootDict.SetString("CFBundleIdentifier", string.Format("{0}.{1}", PlayerSettings.bundleIdentifier, "osx"));
File.WriteAllText(pluginPlistFilePath, pluginPlist.WriteToString());
}
// Edit the source of your generated *.icns file
string icnsSource = Path.Combine(Application.dataPath, "{ICNS_FILE_PATH}");
string icnsDestination = string.Format("{0}/Contents/Resources/PlayerIcon.icns", pathToBuiltProject);
File.Copy(icnsSource, icnsDestination, true);
// Edit the source of your generated *.png file
string playerIconSource = Path.Combine(Application.dataPath, "{PNG_FILE_PATH}");
string playerIconDestination = string.Format("{0}/Contents/Resources/UnityPlayerIcon.png", pathToBuiltProject);
File.Copy(playerIconSource, playerIconDestination, true);
// Remove meta files (I think that only happens in Plugins folder)
foreach(string filePath in Directory.GetFiles(string.Format("{0}/Contents/Plugins", pathToBuiltProject), "*.meta", SearchOption.AllDirectories)) {
File.Delete(filePath);
}
}
Explained:
-
Copies your generated “*.icns” file to “{YOUR_APP}.app/Contents/Resources/PlayerIcon.icns”. If you don’t do this you will get the Unity editor default iconset with default icons.
-
Copies your “UnityPlayerIcon.png” file to “{YOUR_APP}.app/Contents/Resources/UnityPlayerIcon.png”. If you don’t do this you will get the Unity editor default icon.
-
Edits “Info.plist” on “{YOUR_APP}.app/Contents” to:
-
Change values of: CFBundleGetInfoString, CFBundleIdentifier, CFBundleShortVersionString, CFBundleSignature, CFBundleVersion.
-
Add the following keys and values: LSApplicationCategoryType and CFBundleSupportedPlatforms.
If you don’t do this you will have many errors uploading it with Application Loader. For example if you don’t add “CFBundleSupportedPlatforms” key you will get an error saying that your package it’s an ipa or something like that because it seems that it default’s to iOS if that key it’s not present.
- Edit other "*.bundle"s Info.plist that has a “CFBundleIdentifier” to point to your identifier also. I had a problem with AVPro that had it’s own identifier that was not valid. If you don’t do this you will have many errors uploading it with Application Loader saying that the identifier it’s duplicated or it’s not valid.
- Remove the “.meta" files that Unity don’t remove from ".bundle” files (aka plugins). If you don’t do this you will get codesigning error files like: Invalid Signature. A sealed resource is missing or invalid.
-
Publish your app (in my case in x86_x64).
-
Fix permissions:
chmod -R a+xr "{YOUR_APP}.app"
- Create an entitlements file (Example: “my_app.entitlements”) with the following content (the most basic one):
-
Create the certificates (Mac App Store > Mac App Distribution and Mac Spp Store > Mac Installer Distribution): Sign In - Apple
-
Download the certificates and open them to add it to the keychain.
-
Open the keychain and copy (to a txt) from the two certificates the “common name” that you see when you open it (Including “3rd Party Mac Developer…”).
-
Open a terminal on the folder you publish your app and type (there is a “–deep” flag that didn’t work for me so I need to sign every one manually. There is also a “–verify” flag to check for errors):
- Build your product:
-
Upload it with Application loader.
-
If you see warning or errors in any step you need to fix them.
-
Despite of having a green tick saying that it’s okay you can get later an email from Apple regarding some issues with codesigning or similar.
-
If all it’s ok, go to your app and submit it for review.
