diff --git a/desktop/DESKTOP.md b/desktop/DESKTOP.md
index b85ceb14d..e04132b19 100644
--- a/desktop/DESKTOP.md
+++ b/desktop/DESKTOP.md
@@ -1,57 +1,128 @@
Desktop apps
============
-Pokémon Showdown for desktop is made with [node-webkit][1].
+Pokémon Showdown for desktop is made with [NW.js][1].
- [1]: https://github.com/rogerwang/node-webkit
+ [1]: https://nwjs.io/
-There's an `.ico` icon (for Windows) and an `.icns` icon (for Mac) in the
-`graphics-src` directory of this repository. We use these pretty much
-whenever we need icons.
+There's an `.ico` icon (for Windows) and an `.icns` icon (for Mac) in the `graphics-src` directory of this repository. We use these pretty much whenever we need icons.
-Note that node-webkit doesn't support normal window icons in Windows,
-so we have to use a PNG icon to get scaling not to look horribly ugly.
+Note that NW.js doesn't support normal window icons in Windows, so we have to use a PNG icon to get scaling not to look horribly ugly.
Packaging
---------
-node-webkit's wiki contains packaging instructions, but they're strewn
-across their wiki and don't really go into best practices, so I have
-better packaging instructions here.
+NW.js's docs contains [packaging instructions][2], but they're strewn across their wiki and don't really go into best practices, so I have better packaging instructions here.
-For performance, we don't zip up the package on either platform. In Windows,
-the `index.html` and `package.json` files are dropped directly into the
-node-webkit directory, and in OS X the files are dropped into
-`Resources/app.nw`.
+ [2]: http://docs.nwjs.io/en/latest/For%20Users/Package%20and%20Distribute/
+
+For performance, we don't zip up the package on either platform. In Windows, the `index.html` and `package.json` files are dropped directly into the install directory, and in OS X the files are dropped into `Resources/app.nw`.
Packaging for Windows
---------------------
-1. Put a copy of node-webkit (build or extract the prebuilt binary) into this
- folder.
+1. Put a copy of node-webkit (build or extract the prebuilt binary) into this folder.
2. Rename `nw.exe` to `pokemonshowdown.exe`
-3. Edit `pokemonshowdown.exe` with [Resource Hacker][2], and replace the
- node-webkit icon with`icons/pokemonshowdown.ico`
+3. Edit `pokemonshowdown.exe` with [Resource Hacker][3], and replace the node-webkit icon with`icons/pokemonshowdown.ico`
-4. Using [NSIS][2], build `pokemonshowdown.nsi`
+4. You may need to update `pokemonshowdown.nsi` if the file layout has changed; refer to `make-nsis-script.js`
- [2]: http://www.angusj.com/resourcehacker/
- [3]: http://nsis.sourceforge.net/
+5. Using [NSIS][4], build `pokemonshowdown.nsi`
+
+ [3]: http://www.angusj.com/resourcehacker/
+ [4]: http://nsis.sourceforge.net/
Packaging for OS X
------------------
+NOTE: By default, Mac apps will refuse to run, displaying a Gatekeeper warning. Removing the Gatekeeper warning requires an Apple Developer account (costs $99/year) and an annoying notarization process (step 8 to 18).
+
1. Get a copy of node-webkit (build or extract the prebuilt binary)
2. Rename it to `Pokemon Showdown.app`
-3. Replace `Pokemon Showdown.app/Contents/Info.plist` with the `Info.plist`
- in this directory
+3. Update `Pokemon Showdown.app/Contents/Info.plist`, changing:
-4. Replace `Pokemon Showdown.app/Contents/Resources/nw.icns` with the
- icns file in `graphics-src`.
+ - `CFBundleIdentifier` to `com.pokemonshowdown.pokemonshowdown`
+ - `CFBundleName` to `Pokemon Showdown`
+ - `CFBundleDisplayName` to `Pokemon Showdown`
+ - `CFBundleShortVersionString` to the current version, e.g. `0.11`
+ - `CFBundleVersion` to the some sort of version code, I just used the git commit hash
+ - empty the arrays of `CFBundleDocumentTypes`, `CFBundleURLTypes`, `NSUserActivityTypes`, and `UTExportedTypeDeclarations` so they look like ``
+ - (these register the app as able to open these files/URLs, which we _definitely_ do not want to do)
-5. Create a folder `Pokemon Showdown.app/Contents/Resources/app.nw` and put
- `index.html` and `package.json` in it.
+4. Update `Pokemon Showdown.app/Contents/Resources/en.lproj/InfoPlist.strings`,
+ changing:
+
+ - `CFBundleName` to `"Pokemon Showdown"`
+ - `CFBundleDisplayName` to `"Pokemon Showdown"`
+ - `CFBundleGetInfoString` to something like `"Pokemon Showdown 0.11, Copyright 2011-2020 Guangcong Luo and contributors."`
+ - `NSHumanReadableCopyright` to something like `"Copyright 2011-2020 Guangcong Luo and contributors."`
+
+5. Delete all the other `*.lproj` folders (other than `en.lproj`) in `Pokemon Showdown.app/Contents/Resources`
+
+ - (our app is named "Pokemon Showdown" in all languages, we definitely don't want it to be called "nwjs" in other languages)
+
+6. Replace `Pokemon Showdown.app/Contents/Resources/app.icns` and `Pokemon Showdown.app/Contents/Resources/document.icns` with the icns file in `graphics-src`.
+
+7. Create a folder `Pokemon Showdown.app/Contents/Resources/app.nw` and put `index.html` and `package.json` in it.
+
+8. Grab a developer ID certificate (this requires an Apple Developer account costing $99)
+
+ - https://developer.apple.com/account/mac/certificate/certificateList.action
+ - type should be "Developer ID Application"
+
+9. Install the cert in Keychain and remember its "identity" (the part in parentheses)
+
+ - just drag and drop the cert file into Keychain Access
+
+10. Sign the app, set up entitlements, and set it to use Hardened Runtime
+
+ - Edit `sign-mac-app`, setting `APP` to the location of your app, and `IDENTITY` to the identity from step 9
+ - Run `sign-mac-app`
+
+11. Verify the signature
+
+ - `codesign --verify -vvvv "Pokemon Showdown.app"`
+
+12. Zip up the app into `Pokemon Showdown.zip`
+
+ - right-click, Compress, rename to remove the `.app` part
+
+13. Notarize the app, noting the `RequestUUID`
+
+ - `[USERNAME]` is your Apple Developer account username (should be an email address)
+ - `[PASSWORD]` is an app-specific password for your Apple Developer account
+ - get an app-specific password here: https://support.apple.com/en-us/HT204397
+ - `xcrun altool --notarize-app --primary-bundle-id "com.pokemonshowdown.pokemonshowdown" --username "[USERNAME]" --password "[PASSWORD]" --file "Pokemon Showdown.zip"`
+ - this will show a `RequestUUID`, which you'll need
+
+14. Wait for the app to notarize (this takes around 10 minutes in my experience)
+
+15. Check the notarization
+
+ - `[RequestUUID]` is the UUID from above
+ - `xcrun altool --notarization-info [RequestUUID] -u "[USERNAME]"`
+ - it will ask for a password; use the same password as in step 12
+ - the Status line will say either `in progress`, `Package Approved`, or `Package Invalid`
+ - `in progress` - try again in ten minutes
+ - `Package Invalid` - error messages will be in the `LogFileURL` link
+ - `Package Approved` - this is what we're hoping for
+
+16. Staple the notarization
+
+ - `xcrun stapler staple "Pokemon Showdown.app"`
+
+17. Validate the notarization
+
+ - `spctl -a -vvvv "Pokemon Showdown.app"`
+
+18. Delete the un-stapled zip, and create a new zip
+
+Apple's own documentation on the command-line notarization process might be useful:
+
+https://developer.apple.com/documentation/xcode/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow
+
+But it doesn't cover how to set up an existing app for Hardened Runtime (I only figured it out from a random GitHub issue after an hour of Googling).
diff --git a/desktop/Info.plist b/desktop/Info.plist
index beb6a903c..a07e39617 100644
--- a/desktop/Info.plist
+++ b/desktop/Info.plist
@@ -3,46 +3,19 @@
BuildMachineOSBuild
- 12F45
+ 17G3025
CFBundleDevelopmentRegion
en
CFBundleDisplayName
Pokemon Showdown
CFBundleDocumentTypes
-
-
- CFBundleTypeIconFile
- nw.icns
- CFBundleTypeName
- nwjs App
- CFBundleTypeRole
- Viewer
- LSHandlerRank
- Owner
- LSItemContentTypes
-
- io.nwjs.nw.app
-
-
-
- CFBundleTypeName
- Folder
- CFBundleTypeOSTypes
-
- fold
-
- CFBundleTypeRole
- Viewer
- LSHandlerRank
- None
-
-
+
CFBundleExecutable
nwjs
CFBundleIconFile
- nw.icns
+ app.icns
CFBundleIdentifier
- io.nwjs.nw
+ com.pokemonshowdown.pokemonshowdown
CFBundleInfoDictionaryVersion
6.0
CFBundleName
@@ -50,54 +23,53 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 0.3.0
+ 0.11
+ CFBundleSignature
+ NWJS
+ CFBundleURLTypes
+
CFBundleVersion
- 2236.2
+ bf46c879d0
+ DTCompiler
+ com.apple.compilers.llvm.clang.1_0
DTSDKBuild
- 12F37
+ 10.14
DTSDKName
- macosx10.8
+ macosx10.14
DTXcode
- 0511
+ 1000
DTXcodeBuild
- 5B1008
+ 10A255
+ LSEnvironment
+
+ MallocNanoZone
+ 0
+
LSFileQuarantineEnabled
-
+
+ LSHasLocalizedDisplayName
+ 1
LSMinimumSystemVersion
- 10.6.0
+ 10.10.0
+ NSAppleScriptEnabled
+
NSPrincipalClass
- NSApplication
+ BrowserCrApplication
+ NSRequiresAquaSystemAppearance
+
NSSupportsAutomaticGraphicsSwitching
- SCMRevision
- df30fb73b312044486237d93cf96f3606862f2a3
- UTExportedTypeDeclarations
+ NSUserActivityTypes
-
- UTTypeConformsTo
-
- com.pkware.zip-archive
-
- UTTypeDescription
- nwjs App
- UTTypeIconFile
- nw.icns
- UTTypeIdentifier
- io.nwjs.nw.app
- UTTypeReferenceURL
- https://github.com/rogerwang/node-webkit/wiki/How-to-package-and-distribute-your-apps
- UTTypeTagSpecification
-
- com.apple.ostype
- nwjs
- public.filename-extension
-
- nw
-
- public.mime-type
- application/x-nwjs-app
-
-
+ NSUserActivityTypeBrowsingWeb
+ NSUserNotificationAlertStyle
+ banner
+ OSAScriptingDefinition
+ scripting.sdef
+ SCMRevision
+ ac9418ba9c3bd7f6baaffa0b055dfe147e0f8364-refs/branch-heads/3538@{#468}
+ UTExportedTypeDeclarations
+
diff --git a/desktop/make-nsis-script.js b/desktop/make-nsis-script.js
new file mode 100644
index 000000000..c41d241e6
--- /dev/null
+++ b/desktop/make-nsis-script.js
@@ -0,0 +1,195 @@
+/** key order matters for buckets */
+function putInBuckets(files) {
+ let buckets = new Map();
+ buckets.set('', []);
+
+ for (let file of files) {
+ file = file.trim().replace(/\//g, `\\`);
+ if (!file) continue;
+ let splitFiles = file.split(`\\`);
+ for (let i = 1; i < splitFiles.length; i++) {
+ let parent = splitFiles.slice(0, i).join(`\\`);
+ if (buckets.has(parent)) continue;
+ buckets.set(parent, []);
+ }
+ let parent = splitFiles.slice(0, -1).join(`\\`);
+ if (!splitFiles[splitFiles.length - 1]) continue;
+ buckets.get(parent).push(file);
+ }
+
+ return buckets;
+}
+
+function createInstallScript(buckets) {
+ let out = ``;
+ for (const [parent, files] of buckets) {
+ if (parent) out += `CreateDirectory "$INSTDIR\\${parent}"\n`;
+ if (files.length) {
+ if (parent) out += `setOutPath "$INSTDIR\\${parent}"\n`;
+ for (const file of files) {
+ out += `file "${file}"\n`;
+ }
+ }
+ }
+ return out;
+}
+
+function createUninstallScript(buckets) {
+ let out = ``;
+ let reverseEntries = [...buckets.entries()].reverse();
+ for (const [parent, files] of reverseEntries) {
+ for (const file of files) {
+ out += `delete "$INSTDIR\\${file}"\n`;
+ }
+ if (parent) out += `rmDir "$INSTDIR\\${parent}"\n`;
+ }
+ return out;
+}
+
+/*********************************************************************************/
+
+const files = [
+ 'credits.html',
+ 'd3dcompiler_47.dll',
+ 'ffmpeg.dll',
+ 'icons',
+ 'icons/icon_32x32.png',
+ 'icons/installerbg.bmp',
+ 'icons/pokemonshowdown.ico',
+ 'icudtl.dat',
+ 'index.html',
+ 'libEGL.dll',
+ 'libGLESv2.dll',
+ 'locales',
+ 'locales/am.pak',
+ 'locales/am.pak.info',
+ 'locales/ar.pak',
+ 'locales/ar.pak.info',
+ 'locales/bg.pak',
+ 'locales/bg.pak.info',
+ 'locales/bn.pak',
+ 'locales/bn.pak.info',
+ 'locales/ca.pak',
+ 'locales/ca.pak.info',
+ 'locales/cs.pak',
+ 'locales/cs.pak.info',
+ 'locales/da.pak',
+ 'locales/da.pak.info',
+ 'locales/de.pak',
+ 'locales/de.pak.info',
+ 'locales/el.pak',
+ 'locales/el.pak.info',
+ 'locales/en-GB.pak',
+ 'locales/en-GB.pak.info',
+ 'locales/en-US.pak',
+ 'locales/en-US.pak.info',
+ 'locales/es-419.pak',
+ 'locales/es-419.pak.info',
+ 'locales/es.pak',
+ 'locales/es.pak.info',
+ 'locales/et.pak',
+ 'locales/et.pak.info',
+ 'locales/fa.pak',
+ 'locales/fa.pak.info',
+ 'locales/fi.pak',
+ 'locales/fi.pak.info',
+ 'locales/fil.pak',
+ 'locales/fil.pak.info',
+ 'locales/fr.pak',
+ 'locales/fr.pak.info',
+ 'locales/gu.pak',
+ 'locales/gu.pak.info',
+ 'locales/he.pak',
+ 'locales/he.pak.info',
+ 'locales/hi.pak',
+ 'locales/hi.pak.info',
+ 'locales/hr.pak',
+ 'locales/hr.pak.info',
+ 'locales/hu.pak',
+ 'locales/hu.pak.info',
+ 'locales/id.pak',
+ 'locales/id.pak.info',
+ 'locales/it.pak',
+ 'locales/it.pak.info',
+ 'locales/ja.pak',
+ 'locales/ja.pak.info',
+ 'locales/kn.pak',
+ 'locales/kn.pak.info',
+ 'locales/ko.pak',
+ 'locales/ko.pak.info',
+ 'locales/lt.pak',
+ 'locales/lt.pak.info',
+ 'locales/lv.pak',
+ 'locales/lv.pak.info',
+ 'locales/ml.pak',
+ 'locales/ml.pak.info',
+ 'locales/mr.pak',
+ 'locales/mr.pak.info',
+ 'locales/ms.pak',
+ 'locales/ms.pak.info',
+ 'locales/nb.pak',
+ 'locales/nb.pak.info',
+ 'locales/nl.pak',
+ 'locales/nl.pak.info',
+ 'locales/pl.pak',
+ 'locales/pl.pak.info',
+ 'locales/pt-BR.pak',
+ 'locales/pt-BR.pak.info',
+ 'locales/pt-PT.pak',
+ 'locales/pt-PT.pak.info',
+ 'locales/ro.pak',
+ 'locales/ro.pak.info',
+ 'locales/ru.pak',
+ 'locales/ru.pak.info',
+ 'locales/sk.pak',
+ 'locales/sk.pak.info',
+ 'locales/sl.pak',
+ 'locales/sl.pak.info',
+ 'locales/sr.pak',
+ 'locales/sr.pak.info',
+ 'locales/sv.pak',
+ 'locales/sv.pak.info',
+ 'locales/sw.pak',
+ 'locales/sw.pak.info',
+ 'locales/ta.pak',
+ 'locales/ta.pak.info',
+ 'locales/te.pak',
+ 'locales/te.pak.info',
+ 'locales/th.pak',
+ 'locales/th.pak.info',
+ 'locales/tr.pak',
+ 'locales/tr.pak.info',
+ 'locales/uk.pak',
+ 'locales/uk.pak.info',
+ 'locales/vi.pak',
+ 'locales/vi.pak.info',
+ 'locales/zh-CN.pak',
+ 'locales/zh-CN.pak.info',
+ 'locales/zh-TW.pak',
+ 'locales/zh-TW.pak.info',
+ 'natives_blob.bin',
+ 'node.dll',
+ 'notification_helper.exe',
+ 'nw.dll',
+ 'nw.exe',
+ 'nw_100_percent.pak',
+ 'nw_200_percent.pak',
+ 'nw_elf.dll',
+ 'package.json',
+ 'resources.pak',
+ 'swiftshader',
+ 'swiftshader/libEGL.dll',
+ 'swiftshader/libGLESv2.dll',
+ 'v8_context_snapshot.bin',
+];
+
+/*********************************************************************************/
+
+const buckets = putInBuckets(files);
+
+console.log(createInstallScript(buckets));
+
+console.log('');
+
+console.log(createUninstallScript(buckets));
+
diff --git a/desktop/nwjs-entitlements.entitlements b/desktop/nwjs-entitlements.entitlements
new file mode 100644
index 000000000..eb46552e7
--- /dev/null
+++ b/desktop/nwjs-entitlements.entitlements
@@ -0,0 +1,18 @@
+
+
+
+
+ com.apple.security.automation.apple-events
+
+ com.apple.security.cs.allow-dyld-environment-variables
+
+ com.apple.security.cs.allow-jit
+
+ com.apple.security.cs.allow-unsigned-executable-memory
+
+ com.apple.security.cs.disable-executable-page-protection
+
+ com.apple.security.cs.disable-library-validation
+
+
+
diff --git a/desktop/pokemonshowdown.nsi b/desktop/pokemonshowdown.nsi
index a5f907c19..d648ff73e 100755
--- a/desktop/pokemonshowdown.nsi
+++ b/desktop/pokemonshowdown.nsi
@@ -70,20 +70,141 @@ Section "Main" SecMain
setOutPath $INSTDIR
# Files added here should be removed by the uninstaller (see section "uninstall")
- file "pokemonshowdown.exe"
- file "package.json"
- file "index.html"
- file "nw.pak"
+ file "credits.html"
file "d3dcompiler_47.dll"
+ file "ffmpeg.dll"
file "icudtl.dat"
- file "ffmpegsumo.dll"
+ file "index.html"
file "libEGL.dll"
file "libGLESv2.dll"
- file "pdf.dll"
+ file "natives_blob.bin"
+ file "node.dll"
+ file "notification_helper.exe"
+ file "nw.dll"
+ file "pokemonshowdown.exe"
+ file "nw_100_percent.pak"
+ file "nw_200_percent.pak"
+ file "nw_elf.dll"
+ file "package.json"
+ file "resources.pak"
+ file "v8_context_snapshot.bin"
CreateDirectory "$INSTDIR\icons"
setOutPath "$INSTDIR\icons"
file "icons\icon_32x32.png"
+ file "icons\installerbg.bmp"
file "icons\pokemonshowdown.ico"
+ CreateDirectory "$INSTDIR\locales"
+ setOutPath "$INSTDIR\locales"
+ file "locales\am.pak"
+ file "locales\am.pak.info"
+ file "locales\ar.pak"
+ file "locales\ar.pak.info"
+ file "locales\bg.pak"
+ file "locales\bg.pak.info"
+ file "locales\bn.pak"
+ file "locales\bn.pak.info"
+ file "locales\ca.pak"
+ file "locales\ca.pak.info"
+ file "locales\cs.pak"
+ file "locales\cs.pak.info"
+ file "locales\da.pak"
+ file "locales\da.pak.info"
+ file "locales\de.pak"
+ file "locales\de.pak.info"
+ file "locales\el.pak"
+ file "locales\el.pak.info"
+ file "locales\en-GB.pak"
+ file "locales\en-GB.pak.info"
+ file "locales\en-US.pak"
+ file "locales\en-US.pak.info"
+ file "locales\es-419.pak"
+ file "locales\es-419.pak.info"
+ file "locales\es.pak"
+ file "locales\es.pak.info"
+ file "locales\et.pak"
+ file "locales\et.pak.info"
+ file "locales\fa.pak"
+ file "locales\fa.pak.info"
+ file "locales\fi.pak"
+ file "locales\fi.pak.info"
+ file "locales\fil.pak"
+ file "locales\fil.pak.info"
+ file "locales\fr.pak"
+ file "locales\fr.pak.info"
+ file "locales\gu.pak"
+ file "locales\gu.pak.info"
+ file "locales\he.pak"
+ file "locales\he.pak.info"
+ file "locales\hi.pak"
+ file "locales\hi.pak.info"
+ file "locales\hr.pak"
+ file "locales\hr.pak.info"
+ file "locales\hu.pak"
+ file "locales\hu.pak.info"
+ file "locales\id.pak"
+ file "locales\id.pak.info"
+ file "locales\it.pak"
+ file "locales\it.pak.info"
+ file "locales\ja.pak"
+ file "locales\ja.pak.info"
+ file "locales\kn.pak"
+ file "locales\kn.pak.info"
+ file "locales\ko.pak"
+ file "locales\ko.pak.info"
+ file "locales\lt.pak"
+ file "locales\lt.pak.info"
+ file "locales\lv.pak"
+ file "locales\lv.pak.info"
+ file "locales\ml.pak"
+ file "locales\ml.pak.info"
+ file "locales\mr.pak"
+ file "locales\mr.pak.info"
+ file "locales\ms.pak"
+ file "locales\ms.pak.info"
+ file "locales\nb.pak"
+ file "locales\nb.pak.info"
+ file "locales\nl.pak"
+ file "locales\nl.pak.info"
+ file "locales\pl.pak"
+ file "locales\pl.pak.info"
+ file "locales\pt-BR.pak"
+ file "locales\pt-BR.pak.info"
+ file "locales\pt-PT.pak"
+ file "locales\pt-PT.pak.info"
+ file "locales\ro.pak"
+ file "locales\ro.pak.info"
+ file "locales\ru.pak"
+ file "locales\ru.pak.info"
+ file "locales\sk.pak"
+ file "locales\sk.pak.info"
+ file "locales\sl.pak"
+ file "locales\sl.pak.info"
+ file "locales\sr.pak"
+ file "locales\sr.pak.info"
+ file "locales\sv.pak"
+ file "locales\sv.pak.info"
+ file "locales\sw.pak"
+ file "locales\sw.pak.info"
+ file "locales\ta.pak"
+ file "locales\ta.pak.info"
+ file "locales\te.pak"
+ file "locales\te.pak.info"
+ file "locales\th.pak"
+ file "locales\th.pak.info"
+ file "locales\tr.pak"
+ file "locales\tr.pak.info"
+ file "locales\uk.pak"
+ file "locales\uk.pak.info"
+ file "locales\vi.pak"
+ file "locales\vi.pak.info"
+ file "locales\zh-CN.pak"
+ file "locales\zh-CN.pak.info"
+ file "locales\zh-TW.pak"
+ file "locales\zh-TW.pak.info"
+ CreateDirectory "$INSTDIR\swiftshader"
+ setOutPath "$INSTDIR\swiftshader"
+ file "swiftshader\libEGL.dll"
+ file "swiftshader\libGLESv2.dll"
writeUninstaller "$INSTDIR\uninstall.exe"
@@ -124,24 +245,138 @@ Section "Uninstall"
delete "$SMPROGRAMS\${APPNAME}.lnk"
# Remove files
- delete "$INSTDIR\pokemonshowdown.exe"
- delete "$INSTDIR\package.json"
- delete "$INSTDIR\index.html"
+ delete "$INSTDIR\swiftshader\libEGL.dll"
+ delete "$INSTDIR\swiftshader\libGLESv2.dll"
+ rmDir "$INSTDIR\swiftshader"
+ delete "$INSTDIR\locales\am.pak"
+ delete "$INSTDIR\locales\am.pak.info"
+ delete "$INSTDIR\locales\ar.pak"
+ delete "$INSTDIR\locales\ar.pak.info"
+ delete "$INSTDIR\locales\bg.pak"
+ delete "$INSTDIR\locales\bg.pak.info"
+ delete "$INSTDIR\locales\bn.pak"
+ delete "$INSTDIR\locales\bn.pak.info"
+ delete "$INSTDIR\locales\ca.pak"
+ delete "$INSTDIR\locales\ca.pak.info"
+ delete "$INSTDIR\locales\cs.pak"
+ delete "$INSTDIR\locales\cs.pak.info"
+ delete "$INSTDIR\locales\da.pak"
+ delete "$INSTDIR\locales\da.pak.info"
+ delete "$INSTDIR\locales\de.pak"
+ delete "$INSTDIR\locales\de.pak.info"
+ delete "$INSTDIR\locales\el.pak"
+ delete "$INSTDIR\locales\el.pak.info"
+ delete "$INSTDIR\locales\en-GB.pak"
+ delete "$INSTDIR\locales\en-GB.pak.info"
+ delete "$INSTDIR\locales\en-US.pak"
+ delete "$INSTDIR\locales\en-US.pak.info"
+ delete "$INSTDIR\locales\es-419.pak"
+ delete "$INSTDIR\locales\es-419.pak.info"
+ delete "$INSTDIR\locales\es.pak"
+ delete "$INSTDIR\locales\es.pak.info"
+ delete "$INSTDIR\locales\et.pak"
+ delete "$INSTDIR\locales\et.pak.info"
+ delete "$INSTDIR\locales\fa.pak"
+ delete "$INSTDIR\locales\fa.pak.info"
+ delete "$INSTDIR\locales\fi.pak"
+ delete "$INSTDIR\locales\fi.pak.info"
+ delete "$INSTDIR\locales\fil.pak"
+ delete "$INSTDIR\locales\fil.pak.info"
+ delete "$INSTDIR\locales\fr.pak"
+ delete "$INSTDIR\locales\fr.pak.info"
+ delete "$INSTDIR\locales\gu.pak"
+ delete "$INSTDIR\locales\gu.pak.info"
+ delete "$INSTDIR\locales\he.pak"
+ delete "$INSTDIR\locales\he.pak.info"
+ delete "$INSTDIR\locales\hi.pak"
+ delete "$INSTDIR\locales\hi.pak.info"
+ delete "$INSTDIR\locales\hr.pak"
+ delete "$INSTDIR\locales\hr.pak.info"
+ delete "$INSTDIR\locales\hu.pak"
+ delete "$INSTDIR\locales\hu.pak.info"
+ delete "$INSTDIR\locales\id.pak"
+ delete "$INSTDIR\locales\id.pak.info"
+ delete "$INSTDIR\locales\it.pak"
+ delete "$INSTDIR\locales\it.pak.info"
+ delete "$INSTDIR\locales\ja.pak"
+ delete "$INSTDIR\locales\ja.pak.info"
+ delete "$INSTDIR\locales\kn.pak"
+ delete "$INSTDIR\locales\kn.pak.info"
+ delete "$INSTDIR\locales\ko.pak"
+ delete "$INSTDIR\locales\ko.pak.info"
+ delete "$INSTDIR\locales\lt.pak"
+ delete "$INSTDIR\locales\lt.pak.info"
+ delete "$INSTDIR\locales\lv.pak"
+ delete "$INSTDIR\locales\lv.pak.info"
+ delete "$INSTDIR\locales\ml.pak"
+ delete "$INSTDIR\locales\ml.pak.info"
+ delete "$INSTDIR\locales\mr.pak"
+ delete "$INSTDIR\locales\mr.pak.info"
+ delete "$INSTDIR\locales\ms.pak"
+ delete "$INSTDIR\locales\ms.pak.info"
+ delete "$INSTDIR\locales\nb.pak"
+ delete "$INSTDIR\locales\nb.pak.info"
+ delete "$INSTDIR\locales\nl.pak"
+ delete "$INSTDIR\locales\nl.pak.info"
+ delete "$INSTDIR\locales\pl.pak"
+ delete "$INSTDIR\locales\pl.pak.info"
+ delete "$INSTDIR\locales\pt-BR.pak"
+ delete "$INSTDIR\locales\pt-BR.pak.info"
+ delete "$INSTDIR\locales\pt-PT.pak"
+ delete "$INSTDIR\locales\pt-PT.pak.info"
+ delete "$INSTDIR\locales\ro.pak"
+ delete "$INSTDIR\locales\ro.pak.info"
+ delete "$INSTDIR\locales\ru.pak"
+ delete "$INSTDIR\locales\ru.pak.info"
+ delete "$INSTDIR\locales\sk.pak"
+ delete "$INSTDIR\locales\sk.pak.info"
+ delete "$INSTDIR\locales\sl.pak"
+ delete "$INSTDIR\locales\sl.pak.info"
+ delete "$INSTDIR\locales\sr.pak"
+ delete "$INSTDIR\locales\sr.pak.info"
+ delete "$INSTDIR\locales\sv.pak"
+ delete "$INSTDIR\locales\sv.pak.info"
+ delete "$INSTDIR\locales\sw.pak"
+ delete "$INSTDIR\locales\sw.pak.info"
+ delete "$INSTDIR\locales\ta.pak"
+ delete "$INSTDIR\locales\ta.pak.info"
+ delete "$INSTDIR\locales\te.pak"
+ delete "$INSTDIR\locales\te.pak.info"
+ delete "$INSTDIR\locales\th.pak"
+ delete "$INSTDIR\locales\th.pak.info"
+ delete "$INSTDIR\locales\tr.pak"
+ delete "$INSTDIR\locales\tr.pak.info"
+ delete "$INSTDIR\locales\uk.pak"
+ delete "$INSTDIR\locales\uk.pak.info"
+ delete "$INSTDIR\locales\vi.pak"
+ delete "$INSTDIR\locales\vi.pak.info"
+ delete "$INSTDIR\locales\zh-CN.pak"
+ delete "$INSTDIR\locales\zh-CN.pak.info"
+ delete "$INSTDIR\locales\zh-TW.pak"
+ delete "$INSTDIR\locales\zh-TW.pak.info"
+ rmDir "$INSTDIR\locales"
+ delete "$INSTDIR\icons\icon_32x32.png"
+ delete "$INSTDIR\icons\installerbg.bmp"
+ delete "$INSTDIR\icons\pokemonshowdown.ico"
+ rmDir "$INSTDIR\icons"
+ delete "$INSTDIR\credits.html"
delete "$INSTDIR\d3dcompiler_47.dll"
- delete "$INSTDIR\ffmpegsumo.dll"
+ delete "$INSTDIR\ffmpeg.dll"
delete "$INSTDIR\icudtl.dat"
- delete "$INSTDIR\icudt.dll"
+ delete "$INSTDIR\index.html"
delete "$INSTDIR\libEGL.dll"
delete "$INSTDIR\libGLESv2.dll"
- delete "$INSTDIR\nw.pak"
- delete "$INSTDIR\pdf.dll"
- delete "$INSTDIR\icons\icon_32x32.png"
- delete "$INSTDIR\icons\pokemonshowdown.ico"
- delete "$INSTDIR\data\icon_32x32.png"
- delete "$INSTDIR\data\pokemonshowdown.ico"
- rmDir "$INSTDIR\icons"
- rmDir "$INSTDIR\data"
- rmDir "$INSTDIR\locales"
+ delete "$INSTDIR\natives_blob.bin"
+ delete "$INSTDIR\node.dll"
+ delete "$INSTDIR\notification_helper.exe"
+ delete "$INSTDIR\nw.dll"
+ delete "$INSTDIR\pokemonshowdown.exe"
+ delete "$INSTDIR\nw_100_percent.pak"
+ delete "$INSTDIR\nw_200_percent.pak"
+ delete "$INSTDIR\nw_elf.dll"
+ delete "$INSTDIR\package.json"
+ delete "$INSTDIR\resources.pak"
+ delete "$INSTDIR\v8_context_snapshot.bin"
# Always delete uninstaller as the last action
delete $INSTDIR\uninstall.exe
diff --git a/desktop/sign-mac-app b/desktop/sign-mac-app
new file mode 100755
index 000000000..672033ed7
--- /dev/null
+++ b/desktop/sign-mac-app
@@ -0,0 +1,76 @@
+#!/usr/bin/env node
+
+const APP = "path/to/Pokemon Showdown.app";
+const IDENTITY = "identity of Developer ID cert";
+
+/****************************************************************************/
+
+console.log("### finding things to sign");
+
+const fs = require('fs');
+const child_process = require('child_process');
+
+const items = [];
+
+const frameworksDir = `${APP}/Contents/Frameworks/nwjs Framework.framework`;
+
+let currentVersionDir;
+for (const dir of fs.readdirSync(`${frameworksDir}/Versions`)) {
+ if (fs.statSync(`${frameworksDir}/Versions/${dir}`).isDirectory) {
+ currentVersionDir = `${frameworksDir}/Versions/${dir}`;
+ break;
+ }
+}
+if (!currentVersionDir) {
+ console.error(`couldn't find "${frameworksDir}/Versions/[version]"`);
+ process.exit(1);
+}
+for (const file of fs.readdirSync(`${currentVersionDir}`)) {
+ if (file.endsWith('.dylib')) {
+ items.push(`${currentVersionDir}/${file}`);
+ }
+}
+for (const file of fs.readdirSync(`${currentVersionDir}/Helpers`)) {
+ if (/^[a-z0-9_]*$/.test(file) || file.endsWith('.app')) {
+ items.push(`${currentVersionDir}/Helpers/${file}`);
+ }
+}
+for (const file of fs.readdirSync(`${currentVersionDir}/Libraries`)) {
+ if (file.endsWith('.dylib')) {
+ items.push(`${currentVersionDir}/Libraries/${file}`);
+ }
+}
+for (const file of fs.readdirSync(`${currentVersionDir}/XPCServices`)) {
+ if (file.endsWith('.xpc')) {
+ items.push(`${currentVersionDir}/XPCServices/${file}`);
+ }
+}
+items.push(frameworksDir);
+
+/****************************************************************************/
+
+console.log("");
+console.log("### signing");
+
+function exec(cmd) {
+ console.log(cmd);
+ const result = child_process.spawnSync(cmd, {shell: true, stdio: 'inherit'});
+ if (result.status !== 0) {
+ console.log(`Command failed with status ${result.status}`);
+ if (result.error) console.log(result.error);
+ process.exit(1);
+ }
+}
+
+for (const item of items) {
+ exec(`codesign --verbose --force --deep --strict --options runtime --timestamp --sign "${IDENTITY}" --entitlements nwjs-entitlements.entitlements "${item}"`);
+}
+
+exec(`codesign --verbose --force --deep --strict --options runtime --timestamp --sign "${IDENTITY}" --entitlements nwjs-entitlements.entitlements "${APP}"`);
+
+/****************************************************************************/
+
+console.log("");
+console.log("### verifying signature");
+
+exec(`codesign --verify -vvvv "${APP}"`);