diff --git a/.gitmodules b/.gitmodules index 0f203480..30d8d61d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "client/3rd/wireguard-tools"] path = client/3rd/wireguard-tools url = https://github.com/WireGuard/wireguard-tools/ +[submodule "client/3rd/wireguard-apple"] + path = client/3rd/wireguard-apple + url = https://github.com/WireGuard/wireguard-apple diff --git a/client/3rd/wireguard-apple b/client/3rd/wireguard-apple new file mode 160000 index 00000000..23618f99 --- /dev/null +++ b/client/3rd/wireguard-apple @@ -0,0 +1 @@ +Subproject commit 23618f994f17d8ad8f2f65d79b4a1e8a0830b334 diff --git a/client/client.pro b/client/client.pro index 90381c78..3f9bcc51 100644 --- a/client/client.pro +++ b/client/client.pro @@ -68,6 +68,10 @@ HEADERS += \ utils.h \ vpnconnection.h \ protocols/vpnprotocol.h \ + logger.h \ + loghandler.h \ + loglevel.h \ + constants.h SOURCES += \ configurators/cloak_configurator.cpp \ @@ -116,6 +120,9 @@ SOURCES += \ utils.cpp \ vpnconnection.cpp \ protocols/vpnprotocol.cpp \ + logger.cpp \ + loghandler.cpp + RESOURCES += \ resources.qrc @@ -238,6 +245,42 @@ android { ios { message("Client ios build") CONFIG += static + CONFIG += file_copies + + # For the authentication + LIBS += -framework AuthenticationServices + + # For notifications + LIBS += -framework UIKit + LIBS += -framework Foundation + LIBS += -framework StoreKit + LIBS += -framework UserNotifications + + DEFINES += MVPN_IOS + + SOURCES += \ +# platforms/macos/macospingsender.cpp + + OBJECTIVE_SOURCES += \ +# platforms/ios/iosiaphandler.mm \ +# platforms/ios/iosauthenticationlistener.mm \ +# platforms/ios/ioscontroller.mm \ +# platforms/ios/iosdatamigration.mm \ + platforms/ios/iosglue.mm \ +# platforms/ios/iosnotificationhandler.mm \ +# platforms/ios/iosutils.mm \ +# platforms/macos/macoscryptosettings.mm + + HEADERS += \ +# platforms/macos/macospingsender.h + + OBJECTIVE_HEADERS += \ +# platforms/ios/iosiaphandler.h \ +# platforms/ios/iosauthenticationlistener.h \ +# platforms/ios/ioscontroller.h \ +# platforms/ios/iosdatamigration.h \ +# platforms/ios/iosnotificationhandler.h \ +# platforms/ios/iosutils.h Q_ENABLE_BITCODE.value = NO Q_ENABLE_BITCODE.name = ENABLE_BITCODE @@ -278,6 +321,15 @@ ios { LIBS += $$PWD/3rd/OpenSSL/lib/ios/simulator/libcrypto.a LIBS += $$PWD/3rd/OpenSSL/lib/ios/simulator/libssl.a } + + NETWORKEXTENSION=1 + ! build_pass: system(ruby $$PWD/ios/xcode_patcher.rb "$$PWD" "$$OUT_PWD/AmneziaVPN.xcodeproj" "2.0" "2.0.0" "ios" "$$NETWORKEXTENSION"|| echo "Failed to merge xcode with wireguard") + + + +#ruby %{sourceDir}/client/ios/xcode_patcher.rb "%{buildDir}/AmneziaVPN.xcodeproj" "2.0" "2.0.0" "ios" "1" + #cd client/ && /Users/md/Qt/5.15.2/ios/bin/qmake -o Makefile /Users/md/amnezia/desktop-client/client/client.pro -spec macx-ios-clang CONFIG+=iphonesimulator CONFIG+=simulator CONFIG+=qml_debug -after +# %{sourceDir}/client/ios/xcode_patcher.rb %{buildDir}/client/AmneziaVPN.xcodeproj 2.0 2.0.0 ios 1 } diff --git a/client/constants.h b/client/constants.h new file mode 100644 index 00000000..9059eccc --- /dev/null +++ b/client/constants.h @@ -0,0 +1,127 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CONSTANTS_H +#define CONSTANTS_H + +#include + +namespace Constants { + +// Returns true if we are in a production environment. +bool inProduction(); +void setStaging(); + +// Number of msecs for the captive-portal block alert. +constexpr uint32_t CAPTIVE_PORTAL_ALERT_MSEC = 4000; + +// Number of msecs for the unsecured network alert. +constexpr uint32_t UNSECURED_NETWORK_ALERT_MSEC = 4000; + +// Number of recent connections to retain. +constexpr int RECENT_CONNECTIONS_MAX_COUNT = 5; + +#if defined(UNIT_TEST) +# define CONSTEXPR(type, functionName, releaseValue, debugValue, \ + testingValue) \ + inline type functionName() { return testingValue; } +#else +# define CONSTEXPR(type, functionName, releaseValue, debugValue, \ + testingValue) \ + inline type functionName() { \ + return inProduction() ? releaseValue : debugValue; \ + } +#endif + +// Let's refresh the IP address any 10 minutes (in milliseconds). +CONSTEXPR(uint32_t, ipAddressTimerMsec, 600000, 10000, 0) + +// Let's check the connection status any second. +CONSTEXPR(uint32_t, checkStatusTimerMsec, 1000, 1000, 0) + +// Number of points for the charts. +CONSTEXPR(int, chartsMaxPoints, 30, 30, 30); + +// Any 6 hours, a new check +CONSTEXPR(uint32_t, releaseMonitorMsec, 21600000, 4000, 0) + +// in milliseconds, how often we should fetch the server list and the account. +CONSTEXPR(uint32_t, scheduleAccountAndServersTimerMsec, 3600000, 4000, 0) + +// how often we check the captive portal when the VPN is on. +CONSTEXPR(uint32_t, captivePortalRequestTimeoutMsec, 10000, 4000, 0) + +// How fast the animated icon should move +CONSTEXPR(uint32_t, statusIconAnimationMsec, 200, 200, 0) + +// How often glean pings are sent +CONSTEXPR(uint32_t, gleanTimeoutMsec, 1200000, 1000, 0) + +// How often we check the surveys to be executed (no network requests are done +// for this check) +CONSTEXPR(uint32_t, surveyTimerMsec, 300000, 4000, 0) + +#undef CONSTEXPR + +#define PRODBETAEXPR(type, functionName, prod, beta) \ + inline type functionName() { return inProduction() ? prod : beta; } + +constexpr const char* API_PRODUCTION_URL = "https://vpn.mozilla.org"; +constexpr const char* API_STAGING_URL = + "https://stage-vpn.guardian.nonprod.cloudops.mozgcp.net"; + +constexpr const char* LOGO_URL = ":/ui/resources/logo-dock.png"; + +PRODBETAEXPR(const char*, fxaUrl, "https://api.accounts.firefox.com", + "https://api-accounts.stage.mozaws.net") +PRODBETAEXPR( + const char*, balrogUrl, + "https://aus5.mozilla.org/json/1/FirefoxVPN/%1/%2/release/update.json", + "https://stage.balrog.nonprod.cloudops.mozgcp.net/json/1/FirefoxVPN/%1/%2/" + "release-cdntest/update.json"); +PRODBETAEXPR( + const char*, balrogRootCertFingerprint, + "97e8ba9cf12fb3de53cc42a4e6577ed64df493c247b414fea036818d3823560e", + "3c01446abe9036cea9a09acaa3a520ac628f20a7ae32ce861cb2efb70fa0c745"); + +#undef PRODBETAEXPR + +constexpr const char* PLATFORM_NAME = +#if defined(MVPN_IOS) + "ios" +#elif defined(MVPN_MACOS) + "macos" +#elif defined(MVPN_LINUX) + "linux" +#elif defined(MVPN_ANDROID) + "android" +#elif defined(MVPN_WINDOWS) + "windows" +#elif defined(UNIT_TEST) || defined(MVPN_DUMMY) + "dummy" +#else +# error "Unsupported platform" +#endif + ; + +constexpr const char* PLACEHOLDER_USER_DNS = "127.0.0.1"; + +#if defined(MVPN_ADJUST) +// These are the two auto-generated token from the Adjust dashboard for the +// "Subscription Completed" event. We have two since in the Adjust dashboard we +// have defined two apps for iOS and Android with a event token each. +constexpr const char* ADJUST_SUBSCRIPTION_COMPLETED = +# if defined(MVPN_IOS) + "jl72xm" +# elif defined(MVPN_ANDROID) + "o1mn9m" +# else + "" +# endif + ; +#endif + +}; // namespace Constants + +#endif // CONSTANTS_H diff --git a/client/ios/app/AmneziaVPNLaunchScreen.storyboard b/client/ios/app/AmneziaVPNLaunchScreen.storyboard new file mode 100644 index 00000000..92f79f7e --- /dev/null +++ b/client/ios/app/AmneziaVPNLaunchScreen.storyboard @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/ios/app/Images.xcassets/AppIcon.appiconset/Contents.json b/client/ios/app/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..3b3bcf28 --- /dev/null +++ b/client/ios/app/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "filename" : "icon-ios-20@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "icon-ios-20@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "icon-ios-29@2x-1.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "icon-ios-29@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "icon-ios-40@2x-1.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon-ios-40@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "icon-ios-60@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "icon-ios-60@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "icon-ios-20@1x.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "icon-ios-20@2x-1.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "icon-ios-29@1x.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "icon-ios-29@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "icon-ios-40@1x.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "icon-ios-40@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon-ios-76@1x.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "icon-ios-76@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "icon-ios-83.5@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "icon-ios-1024@1x.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-1024@1x.png b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-1024@1x.png new file mode 100644 index 00000000..1933ed99 Binary files /dev/null and b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-1024@1x.png differ diff --git a/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-20@1x.png b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-20@1x.png new file mode 100644 index 00000000..c6c1b888 Binary files /dev/null and b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-20@1x.png differ diff --git a/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-20@2x-1.png b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-20@2x-1.png new file mode 100644 index 00000000..9c9a8f01 Binary files /dev/null and b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-20@2x-1.png differ diff --git a/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-20@2x.png b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-20@2x.png new file mode 100644 index 00000000..9c9a8f01 Binary files /dev/null and b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-20@2x.png differ diff --git a/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-20@3x.png b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-20@3x.png new file mode 100644 index 00000000..5f43b0d4 Binary files /dev/null and b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-20@3x.png differ diff --git a/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-29@1x.png b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-29@1x.png new file mode 100644 index 00000000..b8fa9934 Binary files /dev/null and b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-29@1x.png differ diff --git a/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-29@2x-1.png b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-29@2x-1.png new file mode 100644 index 00000000..7e27a54d Binary files /dev/null and b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-29@2x-1.png differ diff --git a/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-29@2x.png b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-29@2x.png new file mode 100644 index 00000000..7e27a54d Binary files /dev/null and b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-29@2x.png differ diff --git a/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-29@3x.png b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-29@3x.png new file mode 100644 index 00000000..d408007f Binary files /dev/null and b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-29@3x.png differ diff --git a/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-40@1x.png b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-40@1x.png new file mode 100644 index 00000000..2f241c64 Binary files /dev/null and b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-40@1x.png differ diff --git a/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-40@2x-1.png b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-40@2x-1.png new file mode 100644 index 00000000..bae292db Binary files /dev/null and b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-40@2x-1.png differ diff --git a/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-40@2x.png b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-40@2x.png new file mode 100644 index 00000000..bae292db Binary files /dev/null and b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-40@2x.png differ diff --git a/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-40@3x.png b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-40@3x.png new file mode 100644 index 00000000..8cc41427 Binary files /dev/null and b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-40@3x.png differ diff --git a/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-60@2x.png b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-60@2x.png new file mode 100644 index 00000000..8cc41427 Binary files /dev/null and b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-60@2x.png differ diff --git a/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-60@3x.png b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-60@3x.png new file mode 100644 index 00000000..a60b8ff7 Binary files /dev/null and b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-60@3x.png differ diff --git a/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-76@1x.png b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-76@1x.png new file mode 100644 index 00000000..ddf46a91 Binary files /dev/null and b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-76@1x.png differ diff --git a/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-76@2x.png b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-76@2x.png new file mode 100644 index 00000000..f26844e1 Binary files /dev/null and b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-76@2x.png differ diff --git a/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-83.5@2x.png b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-83.5@2x.png new file mode 100644 index 00000000..03a92fae Binary files /dev/null and b/client/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-83.5@2x.png differ diff --git a/client/ios/app/Images.xcassets/Contents.json b/client/ios/app/Images.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/client/ios/app/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/client/ios/app/Info.plist b/client/ios/app/Info.plist new file mode 100644 index 00000000..6648ef7b --- /dev/null +++ b/client/ios/app/Info.plist @@ -0,0 +1,55 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleAllowMixedLocalizations + + CFBundleDisplayName + Mozilla VPN + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIcons + + CFBundleIcons~ipad + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${APP_DISPLAY_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + ITSAppUsesNonExemptEncryption + + LSRequiresIPhoneOS + + LSSupportsOpeningDocumentsInPlace + + UILaunchStoryboardName + AmneziaVPNLaunchScreen + UIRequiredDeviceCapabilities + + UIRequiresFullScreen + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIUserInterfaceStyle + Light + com.wireguard.ios.app_group_id + group.org.mozilla.ios.Guardian + ADJUST_SDK_TOKEN + $(ADJUST_SDK_TOKEN) + + diff --git a/client/ios/app/launch.png b/client/ios/app/launch.png new file mode 100644 index 00000000..33bb9d7d Binary files /dev/null and b/client/ios/app/launch.png differ diff --git a/client/ios/app/main.entitlements b/client/ios/app/main.entitlements new file mode 100644 index 00000000..cb13ea2d --- /dev/null +++ b/client/ios/app/main.entitlements @@ -0,0 +1,16 @@ + + + + + com.apple.developer.networking.networkextension + + packet-tunnel-provider + + com.apple.security.application-groups + + $(GROUP_ID_IOS) + + com.apple.security.files.user-selected.read-write + + + diff --git a/client/ios/networkextension/AmneziaVPNNetworkExtension.entitlements b/client/ios/networkextension/AmneziaVPNNetworkExtension.entitlements new file mode 100644 index 00000000..eded281a --- /dev/null +++ b/client/ios/networkextension/AmneziaVPNNetworkExtension.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.application-groups + + $(GROUP_ID_IOS) + + com.apple.developer.networking.networkextension + + packet-tunnel-provider + + + diff --git a/client/ios/xcode.xconfig b/client/ios/xcode.xconfig new file mode 100644 index 00000000..1b4f1000 --- /dev/null +++ b/client/ios/xcode.xconfig @@ -0,0 +1,13 @@ +DEVELOPMENT_TEAM = + +# MacOS configuration +GROUP_ID_MACOS = <> +APP_ID_MACOS = <> +NETEXT_ID_MACOS = <> +LOGIN_ID_MACOS = <> +NATIVEMESSAGING_ID_MACOS = <> + +# IOS configuration +GROUP_ID_IOS = group.org.mozilla.ios.Guardian +APP_ID_IOS = org.mozilla.ios.FirefoxVPN +NETEXT_ID_IOS = org.mozilla.ios.FirefoxVPN.network-extension diff --git a/client/ios/xcode_patcher.rb b/client/ios/xcode_patcher.rb new file mode 100644 index 00000000..b44b0b56 --- /dev/null +++ b/client/ios/xcode_patcher.rb @@ -0,0 +1,598 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +require 'xcodeproj' + +class XCodeprojPatcher + attr :project + attr :p_root + attr :target_main + attr :target_extension + +# @@project_root +# def self.project_root +# @@project_root +# end + + def run(project_root, file, shortVersion, fullVersion, platform, networkExtension, webExtension, configHash, adjust_sdk_token) + @p_root = project_root + open_project file + open_target_main + + die 'IOS requires networkExtension mode' if not networkExtension and platform == 'ios' + + group = @project.main_group.new_group('Configuration') + @configFile = group.new_file('ios/xcode.xconfig') + + setup_target_main shortVersion, fullVersion, platform, networkExtension, configHash, adjust_sdk_token + + if platform == 'macos' + setup_target_loginitem shortVersion, fullVersion, configHash + setup_target_nativemessaging shortVersion, fullVersion, configHash if webExtension + end + + if networkExtension + setup_target_extension shortVersion, fullVersion, platform, configHash + setup_target_gobridge + else + setup_target_wireguardgo + setup_target_wireguardtools + end + + setup_target_balrog if platform == 'macos' + + @project.save + end + + def open_project(file) + @project = Xcodeproj::Project.open(file) + puts 'Failed to open the project file: ' + file if @project.nil? + die 'Failed to open the project file: ' + file if @project.nil? + end + + def open_target_main + @target_main = @project.targets.find { |target| target.to_s == 'AmneziaVPN' } + return @target_main if not @target_main.nil? + + puts 'Unable to open AmneziaVPN target' + die 'Unable to open AmneziaVPN target' + end + + def setup_target_main(shortVersion, fullVersion, platform, networkExtension, configHash, adjust_sdk_token) + @target_main.build_configurations.each do |config| + config.base_configuration_reference = @configFile + + config.build_settings['LD_RUNPATH_SEARCH_PATHS'] ||= '"$(inherited) @executable_path/../Frameworks"' + config.build_settings['SWIFT_VERSION'] ||= '5.0' + config.build_settings['CLANG_ENABLE_MODULES'] ||= 'YES' + config.build_settings['SWIFT_OBJC_BRIDGING_HEADER'] ||= p_root + "/" + 'macos/app/WireGuard-Bridging-Header.h' + config.build_settings['FRAMEWORK_SEARCH_PATHS'] ||= [ + "$(inherited)", + "$(PROJECT_DIR)/3rd" + ] + + # Versions and names + config.build_settings['MARKETING_VERSION'] ||= shortVersion + config.build_settings['CURRENT_PROJECT_VERSION'] ||= fullVersion + config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] = configHash['APP_ID_MACOS'] if platform == 'macos' + config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] = configHash['APP_ID_IOS'] if platform == 'ios' + config.build_settings['PRODUCT_NAME'] = 'Mozilla VPN' + + # other config + config.build_settings['INFOPLIST_FILE'] ||= p_root + "/" + platform + '/app/Info.plist' + if platform == 'ios' + config.build_settings['CODE_SIGN_ENTITLEMENTS'] ||= p_root + "/" + 'ios/app/main.entitlements' + if adjust_sdk_token != "" + config.build_settings['ADJUST_SDK_TOKEN'] = adjust_sdk_token + end + elsif networkExtension + config.build_settings['CODE_SIGN_ENTITLEMENTS'] ||= p_root + "/" + 'macos/app/app.entitlements' + else + config.build_settings['CODE_SIGN_ENTITLEMENTS'] ||= p_root + "/" + 'macos/app/daemon.entitlements' + end + + config.build_settings['CODE_SIGN_IDENTITY'] ||= 'Apple Development' + config.build_settings['ENABLE_BITCODE'] ||= 'NO' if platform == 'ios' + config.build_settings['SDKROOT'] = 'iphoneos' if platform == 'ios' + config.build_settings['SWIFT_PRECOMPILE_BRIDGING_HEADER'] = 'NO' if platform == 'ios' + + groupId = ""; + if (platform == 'macos') + groupId = configHash['DEVELOPMENT_TEAM'] + "." + configHash['GROUP_ID_MACOS'] + config.build_settings['APP_ID_MACOS'] ||= configHash['APP_ID_MACOS'] + else + groupId = configHash['GROUP_ID_IOS'] + config.build_settings['GROUP_ID_IOS'] ||= configHash['GROUP_ID_IOS'] + end + + config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ + 'GROUP_ID=\"' + groupId + '\"', + "VPN_NE_BUNDLEID=\\\"" + (platform == 'macos' ? configHash['NETEXT_ID_MACOS'] : configHash['NETEXT_ID_IOS']) + "\\\"", + ] + + if config.name == 'Release' + config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] ||= '-Onone' + end + end + + if networkExtension + # WireGuard group + group = @project.main_group.new_group('WireGuard') + + [ + 'macos/gobridge/wireguard-go-version.h', + '3rd/wireguard-apple/Sources/Shared/Keychain.swift', + '3rd/wireguard-apple/Sources/WireGuardKit/IPAddressRange.swift', + '3rd/wireguard-apple/Sources/WireGuardKit/InterfaceConfiguration.swift', + '3rd/wireguard-apple/Sources/Shared/Model/NETunnelProviderProtocol+Extension.swift', + '3rd/wireguard-apple/Sources/WireGuardKit/TunnelConfiguration.swift', + '3rd/wireguard-apple/Sources/Shared/Model/TunnelConfiguration+WgQuickConfig.swift', + '3rd/wireguard-apple/Sources/WireGuardKit/Endpoint.swift', + '3rd/wireguard-apple/Sources/Shared/Model/String+ArrayConversion.swift', + '3rd/wireguard-apple/Sources/WireGuardKit/PeerConfiguration.swift', + '3rd/wireguard-apple/Sources/WireGuardKit/DNSServer.swift', + '3rd/wireguard-apple/Sources/WireGuardApp/LocalizationHelper.swift', + '3rd/wireguard-apple/Sources/Shared/FileManager+Extension.swift', + '3rd/wireguard-apple/Sources/WireGuardKitC/x25519.c', + '3rd/wireguard-apple/Sources/WireGuardKit/PrivateKey.swift', + ].each { |filename| + file = group.new_file(p_root + "/" + filename) + @target_main.add_file_references([file]) + } + + # @target_main + swift integration + group = @project.main_group.new_group('SwiftIntegration') + + [ + 'platforms/ios/ioscontroller.swift', + 'platforms/ios/ioslogger.swift', + ].each { |filename| + file = group.new_file(p_root + "/" + filename) + @target_main.add_file_references([file]) + } + end + + if (platform == 'ios' && adjust_sdk_token != "") + frameworks_group = @project.groups.find { |group| group.display_name == 'Frameworks' } + frameworks_build_phase = @target_main.build_phases.find { |build_phase| build_phase.to_s == 'FrameworksBuildPhase' } + embed_frameworks_build_phase = @target_main.build_phases.find { |build_phase| build_phase.to_s == 'Embed Frameworks' } + + framework_ref = frameworks_group.new_file('3rd/AdjustSdk.framework') + frameworks_build_phase.add_file_reference(framework_ref) + + framework_file = embed_frameworks_build_phase.add_file_reference(framework_ref) + framework_file.settings = { "ATTRIBUTES" => ['RemoveHeadersOnCopy', 'CodeSignOnCopy'] } + + framework_ref = frameworks_group.new_file('AdServices.framework') + frameworks_build_phase.add_file_reference(framework_ref) + + framework_ref = frameworks_group.new_file('iAd.framework') + frameworks_build_phase.add_file_reference(framework_ref) + end + end + + def setup_target_extension(shortVersion, fullVersion, platform, configHash) + @target_extension = @project.new_target(:app_extension, 'WireGuardNetworkExtension', platform == 'macos' ? :osx : :ios) + + @target_extension.build_configurations.each do |config| + config.base_configuration_reference = @configFile + + config.build_settings['LD_RUNPATH_SEARCH_PATHS'] ||= '"$(inherited) @executable_path/../Frameworks"' + config.build_settings['SWIFT_VERSION'] ||= '5.0' + config.build_settings['CLANG_ENABLE_MODULES'] ||= 'YES' + config.build_settings['SWIFT_OBJC_BRIDGING_HEADER'] ||= p_root + "/" + 'macos/networkextension/WireGuardNetworkExtension-Bridging-Header.h' + config.build_settings['SWIFT_PRECOMPILE_BRIDGING_HEADER'] = 'NO' + + # Versions and names + config.build_settings['MARKETING_VERSION'] ||= shortVersion + config.build_settings['CURRENT_PROJECT_VERSION'] ||= fullVersion + config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] ||= configHash['NETEXT_ID_MACOS'] if platform == 'macos' + config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] ||= configHash['NETEXT_ID_IOS'] if platform == 'ios' + config.build_settings['PRODUCT_NAME'] = 'WireGuardNetworkExtension' + + # other configs + config.build_settings['INFOPLIST_FILE'] ||= p_root + "/" + 'macos/networkextension/Info.plist' + config.build_settings['CODE_SIGN_ENTITLEMENTS'] ||= p_root + "/" + platform + '/networkextension/AmneziaVPNNetworkExtension.entitlements' + config.build_settings['CODE_SIGN_IDENTITY'] = 'Apple Development' + + if platform == 'ios' + config.build_settings['ENABLE_BITCODE'] ||= 'NO' + config.build_settings['SDKROOT'] = 'iphoneos' + + config.build_settings['OTHER_LDFLAGS'] ||= [ + "-stdlib=libc++", + "-Wl,-rpath,@executable_path/Frameworks", + "-framework", + "AssetsLibrary", + "-framework", + "MobileCoreServices", + "-lm", + "-framework", + "UIKit", + "-lz", + "-framework", + "OpenGLES", + ] + end + + groupId = ""; + if (platform == 'macos') + groupId = configHash['DEVELOPMENT_TEAM'] + "." + configHash['GROUP_ID_MACOS'] + else + groupId = configHash['GROUP_ID_IOS'] + end + + config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ + # This is needed to compile the iosglue without Qt. + 'NETWORK_EXTENSION=1', + 'GROUP_ID=\"' + groupId + '\"', + ] + + if config.name == 'Release' + config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] ||= '-Onone' + end + + end + + group = @project.main_group.new_group('WireGuardExtension') + [ + '3rd/wireguard-apple/Sources/WireGuardKit/WireGuardAdapter.swift', + '3rd/wireguard-apple/Sources/WireGuardKit/PacketTunnelSettingsGenerator.swift', + '3rd/wireguard-apple/Sources/WireGuardKit/DNSResolver.swift', + '3rd/wireguard-apple/Sources/WireGuardNetworkExtension/ErrorNotifier.swift', + '3rd/wireguard-apple/Sources/Shared/Keychain.swift', + '3rd/wireguard-apple/Sources/Shared/Model/TunnelConfiguration+WgQuickConfig.swift', + '3rd/wireguard-apple/Sources/Shared/Model/NETunnelProviderProtocol+Extension.swift', + '3rd/wireguard-apple/Sources/Shared/Model/String+ArrayConversion.swift', + '3rd/wireguard-apple/Sources/WireGuardKit/TunnelConfiguration.swift', + '3rd/wireguard-apple/Sources/WireGuardKit/IPAddressRange.swift', + '3rd/wireguard-apple/Sources/WireGuardKit/Endpoint.swift', + '3rd/wireguard-apple/Sources/WireGuardKit/DNSServer.swift', + '3rd/wireguard-apple/Sources/WireGuardKit/InterfaceConfiguration.swift', + '3rd/wireguard-apple/Sources/WireGuardKit/PeerConfiguration.swift', + '3rd/wireguard-apple/Sources/Shared/FileManager+Extension.swift', + '3rd/wireguard-apple/Sources/WireGuardKitC/x25519.c', + '3rd/wireguard-apple/Sources/WireGuardKit/Array+ConcurrentMap.swift', + '3rd/wireguard-apple/Sources/WireGuardKit/IPAddress+AddrInfo.swift', + '3rd/wireguard-apple/Sources/WireGuardKit/PrivateKey.swift', + ].each { |filename| + file = group.new_file(p_root + "/" + filename) + @target_extension.add_file_references([file]) + } + # @target_extension + swift integration + group = @project.main_group.new_group('SwiftIntegration') + + [ + 'platforms/ios/iostunnel.swift', + 'platforms/ios/iosglue.mm', + 'platforms/ios/ioslogger.swift', + ].each { |filename| + file = group.new_file(p_root + "/" + filename) + @target_extension.add_file_references([file]) + } + + frameworks_group = @project.groups.find { |group| group.display_name == 'Frameworks' } + frameworks_build_phase = @target_extension.build_phases.find { |build_phase| build_phase.to_s == 'FrameworksBuildPhase' } + + frameworks_build_phase.clear + + framework_ref = frameworks_group.new_file('libwg-go.a') + frameworks_build_phase.add_file_reference(framework_ref) + + framework_ref = frameworks_group.new_file('NetworkExtension.framework') + frameworks_build_phase.add_file_reference(framework_ref) + + # This fails: @target_main.add_dependency @target_extension + container_proxy = @project.new(Xcodeproj::Project::PBXContainerItemProxy) + container_proxy.container_portal = @project.root_object.uuid + container_proxy.proxy_type = Xcodeproj::Constants::PROXY_TYPES[:native_target] + container_proxy.remote_global_id_string = @target_extension.uuid + container_proxy.remote_info = @target_extension.name + + dependency = @project.new(Xcodeproj::Project::PBXTargetDependency) + dependency.name = @target_extension.name + dependency.target = @target_main + dependency.target_proxy = container_proxy + + @target_main.dependencies << dependency + + copy_appex = @target_main.new_copy_files_build_phase + copy_appex.name = 'Copy Network-Extension plugin' + copy_appex.symbol_dst_subfolder_spec = :plug_ins + + appex_file = copy_appex.add_file_reference @target_extension.product_reference + appex_file.settings = { "ATTRIBUTES" => ['RemoveHeadersOnCopy'] } + end + + def setup_target_gobridge + target_gobridge = legacy_target = @project.new(Xcodeproj::Project::PBXLegacyTarget) + + target_gobridge.build_working_directory = p_root + "/" + 'macos/gobridge' + target_gobridge.build_tool_path = 'make' + target_gobridge.pass_build_settings_in_environment = '1' + target_gobridge.build_arguments_string = '$(ACTION)' + target_gobridge.name = 'WireGuardGoBridge' + target_gobridge.product_name = 'WireGuardGoBridge' + + @project.targets << target_gobridge + @target_extension.add_dependency target_gobridge + end + + def setup_target_balrog + target_balrog = legacy_target = @project.new(Xcodeproj::Project::PBXLegacyTarget) + + target_balrog.build_working_directory = p_root + "/" + 'balrog' + target_balrog.build_tool_path = 'make' + target_balrog.pass_build_settings_in_environment = '1' + target_balrog.build_arguments_string = '$(ACTION)' + target_balrog.name = 'WireGuardBalrog' + target_balrog.product_name = 'WireGuardBalrog' + + @project.targets << target_balrog + + frameworks_group = @project.groups.find { |group| group.display_name == 'Frameworks' } + frameworks_build_phase = @target_main.build_phases.find { |build_phase| build_phase.to_s == 'FrameworksBuildPhase' } + + framework_ref = frameworks_group.new_file('balrog/balrog.a') + frameworks_build_phase.add_file_reference(framework_ref) + + # This fails: @target_main.add_dependency target_balrog + container_proxy = @project.new(Xcodeproj::Project::PBXContainerItemProxy) + container_proxy.container_portal = @project.root_object.uuid + container_proxy.proxy_type = Xcodeproj::Constants::PROXY_TYPES[:native_target] + container_proxy.remote_global_id_string = target_balrog.uuid + container_proxy.remote_info = target_balrog.name + + dependency = @project.new(Xcodeproj::Project::PBXTargetDependency) + dependency.name = target_balrog.name + dependency.target = @target_main + dependency.target_proxy = container_proxy + + @target_main.dependencies << dependency + end + + def setup_target_wireguardtools + target_wireguardtools = legacy_target = @project.new(Xcodeproj::Project::PBXLegacyTarget) + + target_wireguardtools.build_working_directory = p_root + "/" + '3rd/wireguard-tools/src' + target_wireguardtools.build_tool_path = 'make' + target_wireguardtools.pass_build_settings_in_environment = '1' + target_wireguardtools.build_arguments_string = '$(ACTION)' + target_wireguardtools.name = 'WireGuardTools' + target_wireguardtools.product_name = 'WireGuardTools' + + @project.targets << target_wireguardtools + + # This fails: @target_main.add_dependency target_wireguardtools + container_proxy = @project.new(Xcodeproj::Project::PBXContainerItemProxy) + container_proxy.container_portal = @project.root_object.uuid + container_proxy.proxy_type = Xcodeproj::Constants::PROXY_TYPES[:native_target] + container_proxy.remote_global_id_string = target_wireguardtools.uuid + container_proxy.remote_info = target_wireguardtools.name + + dependency = @project.new(Xcodeproj::Project::PBXTargetDependency) + dependency.name = target_wireguardtools.name + dependency.target = @target_main + dependency.target_proxy = container_proxy + + @target_main.dependencies << dependency + + copy_wireguardTools = @target_main.new_copy_files_build_phase + copy_wireguardTools.name = 'Copy wireguard-tools' + copy_wireguardTools.symbol_dst_subfolder_spec = :wrapper + copy_wireguardTools.dst_path = 'Contents/Resources/utils' + + group = @project.main_group.new_group('WireGuardTools') + file = group.new_file '3rd/wireguard-tools/src/wg' + + wireguardTools_file = copy_wireguardTools.add_file_reference file + wireguardTools_file.settings = { "ATTRIBUTES" => ['RemoveHeadersOnCopy'] } + end + + def setup_target_wireguardgo + target_wireguardgo = legacy_target = @project.new(Xcodeproj::Project::PBXLegacyTarget) + + target_wireguardgo.build_working_directory = p_root + "/" + '3rd/wireguard-go' + target_wireguardgo.build_tool_path = 'make' + target_wireguardgo.pass_build_settings_in_environment = '1' + target_wireguardgo.build_arguments_string = '$(ACTION)' + target_wireguardgo.name = 'WireGuardGo' + target_wireguardgo.product_name = 'WireGuardGo' + + @project.targets << target_wireguardgo + + # This fails: @target_main.add_dependency target_wireguardgo + container_proxy = @project.new(Xcodeproj::Project::PBXContainerItemProxy) + container_proxy.container_portal = @project.root_object.uuid + container_proxy.proxy_type = Xcodeproj::Constants::PROXY_TYPES[:native_target] + container_proxy.remote_global_id_string = target_wireguardgo.uuid + container_proxy.remote_info = target_wireguardgo.name + + dependency = @project.new(Xcodeproj::Project::PBXTargetDependency) + dependency.name = target_wireguardgo.name + dependency.target = @target_main + dependency.target_proxy = container_proxy + + @target_main.dependencies << dependency + + copy_wireguardGo = @target_main.new_copy_files_build_phase + copy_wireguardGo.name = 'Copy wireguard-go' + copy_wireguardGo.symbol_dst_subfolder_spec = :wrapper + copy_wireguardGo.dst_path = 'Contents/Resources/utils' + + group = @project.main_group.new_group('WireGuardGo') + file = group.new_file '3rd/wireguard-go/wireguard-go' + + wireguardGo_file = copy_wireguardGo.add_file_reference file + wireguardGo_file.settings = { "ATTRIBUTES" => ['RemoveHeadersOnCopy'] } + end + + def setup_target_loginitem(shortVersion, fullVersion, configHash) + return + @target_loginitem = @project.new_target(:application, 'AmneziaVPNLoginItem', :osx) + + @target_loginitem.build_configurations.each do |config| + config.base_configuration_reference = @configFile + + config.build_settings['LD_RUNPATH_SEARCH_PATHS'] ||= '"$(inherited) @executable_path/../Frameworks"' + + # Versions and names + config.build_settings['MARKETING_VERSION'] ||= shortVersion + config.build_settings['CURRENT_PROJECT_VERSION'] ||= fullVersion + config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] ||= configHash['LOGIN_ID_MACOS'] + config.build_settings['PRODUCT_NAME'] = 'AmneziaVPNLoginItem' + + # other configs + config.build_settings['INFOPLIST_FILE'] ||= 'macos/loginitem/Info.plist' + config.build_settings['CODE_SIGN_ENTITLEMENTS'] ||= p_root + "/" + 'macos/loginitem/AmneziaVPNLoginItem.entitlements' + config.build_settings['CODE_SIGN_IDENTITY'] = 'Apple Development' + config.build_settings['SKIP_INSTALL'] = 'YES' + + config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ + 'APP_ID=\"' + configHash['APP_ID_MACOS'] + '\"', + ] + + if config.name == 'Release' + config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] ||= '-Onone' + end + end + + group = @project.main_group.new_group('LoginItem') + [ + 'macos/loginitem/main.m', + ].each { |filename| + file = group.new_file(filename) + @target_loginitem.add_file_references([file]) + } + + # This fails: @target_main.add_dependency @target_loginitem + container_proxy = @project.new(Xcodeproj::Project::PBXContainerItemProxy) + container_proxy.container_portal = @project.root_object.uuid + container_proxy.proxy_type = Xcodeproj::Constants::PROXY_TYPES[:native_target] + container_proxy.remote_global_id_string = @target_loginitem.uuid + container_proxy.remote_info = @target_loginitem.name + + dependency = @project.new(Xcodeproj::Project::PBXTargetDependency) + dependency.name = @target_loginitem.name + dependency.target = @target_main + dependency.target_proxy = container_proxy + + @target_main.dependencies << dependency + + copy_app = @target_main.new_copy_files_build_phase + copy_app.name = 'Copy LoginItem' + copy_app.symbol_dst_subfolder_spec = :wrapper + copy_app.dst_path = 'Contents/Library/LoginItems' + + app_file = copy_app.add_file_reference @target_loginitem.product_reference + app_file.settings = { "ATTRIBUTES" => ['RemoveHeadersOnCopy'] } + end + + def setup_target_nativemessaging(shortVersion, fullVersion, configHash) + @target_nativemessaging = @project.new_target(:application, 'AmneziaVPNNativeMessaging', :osx) + + @target_nativemessaging.build_configurations.each do |config| + config.base_configuration_reference = @configFile + + config.build_settings['LD_RUNPATH_SEARCH_PATHS'] ||= '"$(inherited) @executable_path/../Frameworks"' + + # Versions and names + config.build_settings['MARKETING_VERSION'] ||= shortVersion + config.build_settings['CURRENT_PROJECT_VERSION'] ||= fullVersion + config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] ||= configHash['NATIVEMESSAGING_ID_MACOS'] + config.build_settings['PRODUCT_NAME'] = 'AmneziaVPNNativeMessaging' + + # other configs + config.build_settings['INFOPLIST_FILE'] ||= p_root + "/" + 'macos/nativeMessaging/Info.plist' + config.build_settings['CODE_SIGN_ENTITLEMENTS'] ||= p_root + "/" + 'macos/nativeMessaging/AmneziaVPNNativeMessaging.entitlements' + config.build_settings['CODE_SIGN_IDENTITY'] = 'Apple Development' + config.build_settings['SKIP_INSTALL'] = 'YES' + end + + group = @project.main_group.new_group('NativeMessaging') + [ + 'extension/app/constants.h', + 'extension/app/handler.cpp', + 'extension/app/handler.h', + 'extension/app/json.hpp', + 'extension/app/logger.cpp', + 'extension/app/logger.h', + 'extension/app/main.cpp', + 'extension/app/vpnconnection.cpp', + 'extension/app/vpnconnection.h', + ].each { |filename| + file = group.new_file(p_root + "/" + filename) + @target_nativemessaging.add_file_references([file]) + } + + # This fails: @target_main.add_dependency @target_nativemessaging + container_proxy = @project.new(Xcodeproj::Project::PBXContainerItemProxy) + container_proxy.container_portal = @project.root_object.uuid + container_proxy.proxy_type = Xcodeproj::Constants::PROXY_TYPES[:native_target] + container_proxy.remote_global_id_string = @target_nativemessaging.uuid + container_proxy.remote_info = @target_nativemessaging.name + + dependency = @project.new(Xcodeproj::Project::PBXTargetDependency) + dependency.name = @target_nativemessaging.name + dependency.target = @target_main + dependency.target_proxy = container_proxy + + @target_main.dependencies << dependency + + copy_app = @target_main.new_copy_files_build_phase + copy_app.name = 'Copy LoginItem' + copy_app.symbol_dst_subfolder_spec = :wrapper + copy_app.dst_path = 'Contents/Library/NativeMessaging' + + app_file = copy_app.add_file_reference @target_nativemessaging.product_reference + app_file.settings = { "ATTRIBUTES" => ['RemoveHeadersOnCopy'] } + + copy_nativeMessagingManifest = @target_main.new_copy_files_build_phase + copy_nativeMessagingManifest.name = 'Copy native messaging manifest' + copy_nativeMessagingManifest.symbol_dst_subfolder_spec = :wrapper + copy_nativeMessagingManifest.dst_path = 'Contents/Resources/utils' + + group = @project.main_group.new_group('WireGuardHelper') + file = group.new_file 'extension/app/manifests/macos/mozillavpn.json' + + nativeMessagingManifest_file = copy_nativeMessagingManifest.add_file_reference file + nativeMessagingManifest_file.settings = { "ATTRIBUTES" => ['RemoveHeadersOnCopy'] } + end + + def die(msg) + print $msg + exit 1 + end +end + +if ARGV.length < 4 || (ARGV[4] != "ios" && ARGV[4] != "macos") + puts "Usage: