From 987cbade7995d534d6c86ba119de1b23c658d047 Mon Sep 17 00:00:00 2001 From: Adrian Castro <22133246+castdrian@users.noreply.github.com> Date: Mon, 2 Dec 2024 02:27:30 +0100 Subject: [PATCH 1/2] feat: fix file picker when sideloaded (yes really) --- .github/workflows/deploy.yml | 2 +- .gitignore | 6 ++ .vscode/settings.json | 5 ++ .vscode/tasks.json | 11 +++ Sources/Sideloading.x | 97 +++++++++++------------- build-local.sh | 143 +++++++++++++++++++++++++++++++++++ control | 2 +- 7 files changed, 213 insertions(+), 53 deletions(-) create mode 100755 build-local.sh diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 21e5ea5..f4d9e9b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -193,7 +193,7 @@ jobs: - name: Inject tweak and extension run: | - cyan -duws -i discord.ipa -o ${{ env.APP_NAME }}.ipa -f ${{ env.DEB_FILE_NAME }} OpenInDiscord/build/OpenInDiscord.appex + cyan -duwsgq -i discord.ipa -o ${{ env.APP_NAME }}.ipa -f ${{ env.DEB_FILE_NAME }} OpenInDiscord/build/OpenInDiscord.appex - name: Upload ipa as artifact uses: actions/upload-artifact@v4 diff --git a/.gitignore b/.gitignore index b86e722..2b2117c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,9 @@ xcuserdata/ .theos/ packages/ .DS_Store + +venv/ +patcher +ipa-icons.zip +*.ipa +OpenInDiscord/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 6429d1f..92927a2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,6 +14,11 @@ "label": "$(cloud-upload) AirDrop Tweak", "task": "AirDrop Tweak", "tooltip": "AirDrop Tweak" + }, + { + "label": "$(package) Build IPA", + "task": "Build IPA", + "tooltip": "Build IPA locally" } ], "externalFormatters.languages": { diff --git a/.vscode/tasks.json b/.vscode/tasks.json index fdf2dbe..4c2490c 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -46,6 +46,17 @@ "close": true }, "dependsOn": ["Build Tweak"] + }, + { + "type": "shell", + "label": "Build IPA", + "command": "chmod +x build-local.sh && ./build-local.sh", + "presentation": { + "panel": "shared", + "showReuseMessage": false, + "clear": true, + "reveal": "always" + } } ] } \ No newline at end of file diff --git a/Sources/Sideloading.x b/Sources/Sideloading.x index 45121e7..5739a2b 100644 --- a/Sources/Sideloading.x +++ b/Sources/Sideloading.x @@ -4,6 +4,7 @@ #import #import #import +#import #import typedef struct __CMSDecoder *CMSDecoderRef; @@ -18,7 +19,6 @@ extern OSStatus CMSDecoderCopyContent(CMSDecoderRef cmsDecoder, CFDataRef *conte #define DISCORD_NAME @"Discord" typedef NS_ENUM(NSInteger, BundleIDError) { - BundleIDErrorFiles, BundleIDErrorIcon, BundleIDErrorPasskey }; @@ -29,11 +29,10 @@ static void showBundleIDError(BundleIDError error) { void (^completion)(void) = nil; switch (error) { - case BundleIDErrorFiles: case BundleIDErrorIcon: message = @"For this to work change the Bundle ID so that it matches your " @"provisioning profile's App ID (excluding the Team ID prefix)."; - title = error == BundleIDErrorFiles ? @"Cannot Access Files" : @"Cannot Change Icon"; + title = @"Cannot Change Icon"; break; case BundleIDErrorPasskey: message = @"Passkeys are not supported when sideloading Discord. " @@ -46,41 +45,6 @@ static void showBundleIDError(BundleIDError error) { showErrorAlert(title, message, completion); } -static NSString *getProvisioningAppID(void) { - NSString *provisionPath = [NSBundle.mainBundle pathForResource:@"embedded" - ofType:@"mobileprovision"]; - if (!provisionPath) - return nil; - NSData *provisionData = [NSData dataWithContentsOfFile:provisionPath]; - if (!provisionData) - return nil; - CMSDecoderRef decoder = NULL; - CMSDecoderCreate(&decoder); - CMSDecoderUpdateMessage(decoder, provisionData.bytes, provisionData.length); - CMSDecoderFinalizeMessage(decoder); - CFDataRef dataRef = NULL; - CMSDecoderCopyContent(decoder, &dataRef); - NSData *data = (__bridge_transfer NSData *)dataRef; - if (decoder) - CFRelease(decoder); - NSError *error = nil; - id plist = [NSPropertyListSerialization propertyListWithData:data - options:0 - format:NULL - error:&error]; - if (!plist || ![plist isKindOfClass:[NSDictionary class]]) - return nil; - NSString *appID = plist[@"Entitlements"][@"application-identifier"]; - if (!appID) - return nil; - NSArray *components = [appID componentsSeparatedByString:@"."]; - if (components.count > 1) { - return [[components subarrayWithRange:NSMakeRange(1, components.count - 1)] - componentsJoinedByString:@"."]; - } - return nil; -} - static NSString *getAccessGroupID(void) { NSDictionary *query = @{ (__bridge NSString *)kSecClass : (__bridge NSString *)kSecClassGenericPassword, @@ -180,22 +144,53 @@ static BOOL isSelfCall(void) { } %end -%hook UIViewController -- (void)presentViewController:(UIViewController *)viewControllerToPresent - animated:(BOOL)flag - completion:(void (^)(void))completion { - if ([viewControllerToPresent isKindOfClass:[UIDocumentPickerViewController class]]) { - NSString *provisioningAppID = getProvisioningAppID(); - NSString *currentBundleID = [[NSBundle mainBundle] bundleIdentifier]; - - if (provisioningAppID && ![provisioningAppID isEqualToString:currentBundleID]) { - BunnyLog(@"Intercepted UIDocumentPickerViewController presentation"); - showBundleIDError(BundleIDErrorFiles); - return; - } +// https://github.com/khanhduytran0/LiveContainer/blob/main/TweakLoader/DocumentPicker.m +%hook UIDocumentPickerViewController + +- (instancetype)initForOpeningContentTypes:(NSArray *)contentTypes asCopy:(BOOL)asCopy { + BOOL shouldMultiselect = NO; + if ([contentTypes count] == 1 && contentTypes[0] == UTTypeFolder) { + shouldMultiselect = YES; + } + + NSArray *contentTypesNew = @[ UTTypeItem, UTTypeFolder ]; + + UIDocumentPickerViewController *ans = %orig(contentTypesNew, YES); + if (shouldMultiselect) { + [ans setAllowsMultipleSelection:YES]; + } + return ans; +} + +- (instancetype)initWithDocumentTypes:(NSArray *)contentTypes inMode:(NSUInteger)mode { + return [self initForOpeningContentTypes:contentTypes asCopy:(mode == 1 ? NO : YES)]; +} + +- (void)setAllowsMultipleSelection:(BOOL)allowsMultipleSelection { + if ([self allowsMultipleSelection]) { + return; } + %orig(YES); +} + +%end + +%hook UIDocumentBrowserViewController + +- (instancetype)initForOpeningContentTypes:(NSArray *)contentTypes { + NSArray *contentTypesNew = @[ UTTypeItem, UTTypeFolder ]; + return %orig(contentTypesNew); +} + +%end + +%hook NSURL + +- (BOOL)startAccessingSecurityScopedResource { %orig; + return YES; } + %end %hook ASAuthorizationController diff --git a/build-local.sh b/build-local.sh new file mode 100755 index 0000000..a9446ef --- /dev/null +++ b/build-local.sh @@ -0,0 +1,143 @@ +#!/bin/bash + +RED='\033[0;31m' +GREEN='\033[0;32m' +BLUE='\033[0;34m' +NC='\033[0m' + +print_status() { + echo -e "${BLUE}[*]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[+]${NC} $1" +} + +print_error() { + echo -e "${RED}[-]${NC} $1" +} + +IPA_FILE=$(find . -maxdepth 1 -name "*.ipa" -print -quit) + +if [ -z "$IPA_FILE" ]; then + print_status "No IPA found. Please enter Discord IPA URL:" + read DISCORD_URL + + if [ -z "$DISCORD_URL" ]; then + print_error "No URL provided" + exit 1 + fi + + print_status "Downloading Discord IPA..." + curl -L -o discord.ipa "$DISCORD_URL" + + if [ $? -ne 0 ]; then + print_error "Failed to download Discord IPA" + exit 1 + fi + IPA_FILE="discord.ipa" + print_success "Downloaded Discord IPA" +fi + +print_status "Building tweak..." +make package FINALPACKAGE=1 + +if [ $? -ne 0 ]; then + print_error "Failed to build tweak" + exit 1 +fi +print_success "Built tweak" + +print_status "Downloading IPA icons..." +curl -L -o ipa-icons.zip https://raw.githubusercontent.com/pyoncord/assets/main/ipa-icons.zip + +if [ $? -ne 0 ]; then + print_error "Failed to download IPA icons" + exit 1 +fi +print_success "Downloaded IPA icons" + +print_status "Downloading patcher..." +curl -L -o patcher https://github.com/amsyarasyiq/bunny-ipa-patcher/releases/download/release-pyon/patcher.mac-amd64 +chmod +x patcher + +if [ $? -ne 0 ]; then + print_error "Failed to download patcher" + exit 1 +fi +print_success "Downloaded patcher" + +print_status "Cloning Safari extension..." +rm -rf OpenInDiscord +git clone https://github.com/castdrian/OpenInDiscord + +if [ $? -ne 0 ]; then + print_error "Failed to clone Safari extension" + exit 1 +fi +print_success "Cloned Safari extension" + +print_status "Building Safari extension..." +cd OpenInDiscord +xcodebuild build \ + -target "OpenInDiscord Extension" \ + -configuration Release \ + -sdk iphoneos \ + CONFIGURATION_BUILD_DIR="build" \ + PRODUCT_NAME="OpenInDiscord" \ + PRODUCT_BUNDLE_IDENTIFIER="com.hammerandchisel.discord.OpenInDiscord" \ + PRODUCT_MODULE_NAME="OpenInDiscordExt" \ + SKIP_INSTALL=NO \ + DEVELOPMENT_TEAM="" \ + CODE_SIGN_IDENTITY="" \ + CODE_SIGNING_REQUIRED=NO \ + CODE_SIGNING_ALLOWED=NO \ + ONLY_ACTIVE_ARCH=NO +cd .. + +if [ $? -ne 0 ]; then + print_error "Failed to build Safari extension" + exit 1 +fi +print_success "Built Safari extension" + +print_status "Patching IPA..." +./patcher -d "$IPA_FILE" -o discord-patched.ipa -i ./ipa-icons.zip + +if [ $? -ne 0 ]; then + print_error "Failed to patch IPA" + exit 1 +fi +print_success "Patched IPA" + +print_status "Setting up Python environment..." +python3 -m venv venv +source venv/bin/activate +pip install --force-reinstall https://github.com/asdfzxcvbn/pyzule-rw/archive/main.zip Pillow + +if [ $? -ne 0 ]; then + print_error "Failed to install cyan" + exit 1 +fi +print_success "Installed cyan" + +NAME=$(grep '^Name:' control | cut -d ' ' -f 2) +PACKAGE=$(grep '^Package:' control | cut -d ' ' -f 2) +VERSION=$(grep '^Version:' control | cut -d ' ' -f 2) +DEB_FILE="packages/${PACKAGE}_${VERSION}_iphoneos-arm.deb" + +print_status "Injecting tweak..." +cyan -duwsgq -i discord-patched.ipa -o "$NAME.ipa" -f "$DEB_FILE" OpenInDiscord/build/OpenInDiscord.appex + +if [ $? -ne 0 ]; then + print_error "Failed to inject tweak" + exit 1 +fi + +deactivate +print_success "Successfully created $NAME.ipa" + +print_status "Cleaning up..." +rm -f patcher ipa-icons.zip discord-patched.ipa + +print_success "Successfully created $NAME.ipa" \ No newline at end of file diff --git a/control b/control index 57d9b6e..f83f91d 100644 --- a/control +++ b/control @@ -1,6 +1,6 @@ Package: app.pyoncord Name: Bunny -Version: 0.6.0 +Version: 0.7.0 Architecture: iphoneos-arm Description: A client modification for Discord's mobile app. Maintainer: Adrian Castro, Pylix From 072a2a7e14f91508e287cb0553ad435960a95ca3 Mon Sep 17 00:00:00 2001 From: Adrian Castro <22133246+castdrian@users.noreply.github.com> Date: Mon, 2 Dec 2024 02:31:11 +0100 Subject: [PATCH 2/2] chore: Update README.md --- README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.md b/README.md index de142c9..9b64086 100644 --- a/README.md +++ b/README.md @@ -33,10 +33,6 @@ To resolve the fixable issues, you need to match the app's bundle ID with your p ✓ - - Cannot select items via Files app - ✓ - Cannot share items to Discord ✗ @@ -45,10 +41,6 @@ To resolve the fixable issues, you need to match the app's bundle ID with your p Cannot use passkeys ✗ - - - - ## Doing this will break notifications if the app is backgrounded or closed