From a1d5966f2751d207f3d368991e4d1411f4077793 Mon Sep 17 00:00:00 2001 From: Adrian Castro <22133246+castdrian@users.noreply.github.com> Date: Fri, 15 Nov 2024 23:11:46 +0100 Subject: [PATCH 01/10] feat: adjust workflow to build and inject app extension --- .github/workflows/deploy.yml | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 0d910e9..9f0fd16 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -123,6 +123,30 @@ jobs: run: | curl -L -o ipa-icons.zip https://raw.githubusercontent.com/pyoncord/assets/main/ipa-icons.zip + - name: Clone OpenInDiscord + uses: actions/checkout@v4 + with: + repository: castdrian/OpenInDiscord + path: OpenInDiscord + + - name: Build Safari Extension + run: | + cd OpenInDiscord + xcodebuild build \ + -target "OpenInDiscord Extension" \ + -configuration Release \ + -sdk iphoneos \ + CONFIGURATION_BUILD_DIR="$(pwd)/build" \ + PRODUCT_NAME="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 .. + - name: Extract Values run: | NAME=$(grep '^Name:' control | cut -d ' ' -f 2) @@ -143,15 +167,15 @@ jobs: - name: Install cyan run: pip install --force-reinstall https://github.com/asdfzxcvbn/pyzule-rw/archive/main.zip Pillow - - name: Inject tweak + - name: Inject tweak and extension run: | - cyan -duws -i discord.ipa -o ${{ github.workspace }}/${{ env.APP_NAME }}.ipa -f ${{ github.workspace }}/${{ env.DEB_FILE_NAME }} + cyan -duws -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 with: name: ipa - path: ${{ github.workspace }}/${{ env.APP_NAME }}.ipa + path: ${{ env.APP_NAME }}.ipa release-app: if: ${{ github.event.inputs.release == 'true' }} From cb1bec32d5fa94dbac91283aebd743ac77a18683 Mon Sep 17 00:00:00 2001 From: Adrian Castro <22133246+castdrian@users.noreply.github.com> Date: Fri, 15 Nov 2024 23:18:27 +0100 Subject: [PATCH 02/10] chore: clean up workflow --- .github/workflows/deploy.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9f0fd16..c75f68f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -83,11 +83,11 @@ jobs: - name: Set DEB path run: | if [ "${{ env.DEB_DOWNLOADED }}" == "true" ]; then - echo "DEB_PATH=${{ github.workspace }}/*-arm.deb" >> $GITHUB_ENV - echo "ROOTLESS_DEB_PATH=${{ github.workspace }}/*-arm64.deb" >> $GITHUB_ENV + echo "DEB_PATH=*-arm.deb" >> $GITHUB_ENV + echo "ROOTLESS_DEB_PATH=*-arm64.deb" >> $GITHUB_ENV else - echo "DEB_PATH=${{ github.workspace }}/packages/*-arm.deb" >> $GITHUB_ENV - echo "ROOTLESS_DEB_PATH=${{ github.workspace }}/packages/*-arm64.deb" >> $GITHUB_ENV + echo "DEB_PATH=packages/*-arm.deb" >> $GITHUB_ENV + echo "ROOTLESS_DEB_PATH=packages/*-arm64.deb" >> $GITHUB_ENV fi - name: Upload package @@ -129,6 +129,11 @@ jobs: repository: castdrian/OpenInDiscord path: OpenInDiscord + - name: Setup Xcode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: latest-stable + - name: Build Safari Extension run: | cd OpenInDiscord @@ -136,7 +141,7 @@ jobs: -target "OpenInDiscord Extension" \ -configuration Release \ -sdk iphoneos \ - CONFIGURATION_BUILD_DIR="$(pwd)/build" \ + CONFIGURATION_BUILD_DIR="build" \ PRODUCT_NAME="OpenInDiscord" \ PRODUCT_MODULE_NAME="OpenInDiscordExt" \ SKIP_INSTALL=NO \ From 19f2ae6507a8d78700e2a5ee6e3661aa21d0afe5 Mon Sep 17 00:00:00 2001 From: Adrian Castro <22133246+castdrian@users.noreply.github.com> Date: Fri, 15 Nov 2024 23:21:55 +0100 Subject: [PATCH 03/10] chore: bump xcode version --- .github/workflows/deploy.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c75f68f..ee18b6b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -129,13 +129,9 @@ jobs: repository: castdrian/OpenInDiscord path: OpenInDiscord - - name: Setup Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: latest-stable - - name: Build Safari Extension run: | + sudo xcode-select -switch /Applications/Xcode_16.app/Contents/Developer cd OpenInDiscord xcodebuild build \ -target "OpenInDiscord Extension" \ From b31b0eee8ba4b51e43a4509327bb9021ca2740cf Mon Sep 17 00:00:00 2001 From: Adrian Castro <22133246+castdrian@users.noreply.github.com> Date: Fri, 15 Nov 2024 23:54:33 +0100 Subject: [PATCH 04/10] chore: bump macos runner --- .github/workflows/deploy.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ee18b6b..90995b1 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -20,7 +20,7 @@ env: jobs: build-tweak: - runs-on: macos-14 + runs-on: macos-15 steps: - name: Checkout code @@ -103,7 +103,7 @@ jobs: path: ${{ env.ROOTLESS_DEB_PATH }} build-ipa: - runs-on: macos-14 + runs-on: macos-15 needs: build-tweak steps: @@ -131,7 +131,6 @@ jobs: - name: Build Safari Extension run: | - sudo xcode-select -switch /Applications/Xcode_16.app/Contents/Developer cd OpenInDiscord xcodebuild build \ -target "OpenInDiscord Extension" \ @@ -180,7 +179,7 @@ jobs: release-app: if: ${{ github.event.inputs.release == 'true' }} - runs-on: macos-14 + runs-on: macos-15 needs: build-ipa permissions: contents: write @@ -228,7 +227,7 @@ jobs: app-repo: if: ${{ github.event.inputs.release == 'true' }} continue-on-error: true - runs-on: macos-14 + runs-on: macos-15 needs: release-app steps: - name: Checkout code From 28c911778a0c77f859dcd5fd002da2307c0564c7 Mon Sep 17 00:00:00 2001 From: Adrian Castro <22133246+castdrian@users.noreply.github.com> Date: Fri, 15 Nov 2024 23:58:54 +0100 Subject: [PATCH 05/10] chore: xcbeautify --- .github/workflows/deploy.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 90995b1..11f71d5 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -144,8 +144,7 @@ jobs: CODE_SIGN_IDENTITY="" \ CODE_SIGNING_REQUIRED=NO \ CODE_SIGNING_ALLOWED=NO \ - ONLY_ACTIVE_ARCH=NO - cd .. + ONLY_ACTIVE_ARCH=NO | xcbeautify - name: Extract Values run: | From 394a089e621a0ba5f6c1233745de9a083463c6fd Mon Sep 17 00:00:00 2001 From: Adrian Castro <22133246+castdrian@users.noreply.github.com> Date: Sat, 16 Nov 2024 00:22:48 +0100 Subject: [PATCH 06/10] chore: linting and formatting --- .vscode/extensions.json | 9 ++ .vscode/logos-format.py | 64 ++++++++ .vscode/settings.json | 23 ++- Headers/Fonts.h | 3 +- Headers/LoaderConfig.h | 6 +- Headers/RCTCxxBridge.h | 6 +- Headers/Theme.h | 5 +- Sources/Fonts.x | 281 ++++++++++++++++++--------------- Sources/LoaderConfig.m | 162 ++++++++++--------- Sources/Sideloading.x | 113 ++++++++------ Sources/Theme.m | 146 ++++++++--------- Sources/Tweak.x | 339 +++++++++++++++++++++++----------------- Sources/Utils.m | 124 ++++++++------- 13 files changed, 748 insertions(+), 533 deletions(-) create mode 100644 .vscode/extensions.json create mode 100644 .vscode/logos-format.py diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..49f79d6 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "tale.logos-vscode", + "spencerwmiles.vscode-task-buttons", + "redhat.vscode-yaml", + "davidanson.vscode-markdownlint", + "SteefH.external-formatters" + ] +} \ No newline at end of file diff --git a/.vscode/logos-format.py b/.vscode/logos-format.py new file mode 100644 index 0000000..d6b5892 --- /dev/null +++ b/.vscode/logos-format.py @@ -0,0 +1,64 @@ +import sys +import re +from subprocess import Popen, PIPE, STDOUT + +# block level +# hook -> replace with @logosformathook with ; at the end +# end -> replace with @logosformatend with ; at the end +# property -> replace with @logosformatproperty with NO ; at the end. Special case for block level +# new -> replce with @logosformatnew with ; at the end +# group -> replace with @logosformatgroup with ; at the end +# subclass -> replace with @logosformatsubclass with ; at the end +# top level +# config -> replace with @logosformatconfig +# hookf -> replace with @logosformathookf +# ctor -> replace with @logosformatctor +# dtor -> replace with @logosformatdtor + +# function level +# init -> replace with @logosformatinit +# c -> replace with @logosformatc +# orig -> replace with @logosformatorig +# log -> replace with @logosformatlog + +specialFilterList = ["%hook", "%end", "%new", "%group", "%subclass"] +filterList = [ + "%property", + "%config", + "%hookf", + "%ctor", + "%dtor", + "%init", + "%c", + "%orig", + "%log", +] + + +fileContentsList = sys.stdin.read().splitlines() +newList = [] + +for line in fileContentsList: + for token in filterList: + if token in line: + line = re.sub(rf"%({token[1:]})\b", r"@logosformat\1", line) + for token in specialFilterList: + if token in line: + line = re.sub(rf"%({token[1:]})\b", r"@logosformat\1", line) + ";" + newList.append(line) + +command = ["clang-format"] + sys.argv[1:] +process = Popen(command, stdout=PIPE, stderr=None, stdin=PIPE) +stdoutData = process.communicate(input="\n".join(newList).encode())[0] +refinedList = stdoutData.decode().splitlines() + + +for line in refinedList: + if "@logosformat" in line: + fix = line.replace("@logosformat", "%") + if any(token in fix for token in specialFilterList): + print(fix.replace(";","")) + else: + print(fix) + else: + print(line) diff --git a/.vscode/settings.json b/.vscode/settings.json index 2cf9559..a98c7b1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,4 +11,25 @@ "tooltip": "Make and copy Tweak" } ], -} + "externalFormatters.languages": { + "logos": { + "command": "python3", + "arguments": [ + "../.vscode/logos-format.py", + "--assume-filename", + "objc" + ] + } + }, + "yaml.schemas": { + "https://json.schemastore.org/github-workflow.json": [ + "*.github/workflows/*.yaml", + "*.github/workflows/*.yml", + ], + "https://json.schemastore.org/github-action.json": [ + "action.yaml", + "action.yml", + ] + }, + "editor.formatOnSave": true +} \ No newline at end of file diff --git a/Headers/Fonts.h b/Headers/Fonts.h index d1d1228..d2f1f78 100644 --- a/Headers/Fonts.h +++ b/Headers/Fonts.h @@ -2,4 +2,5 @@ #import extern NSMutableDictionary *fontMap; -void patchFonts(NSDictionary *mainFonts, NSString *fontDefName); \ No newline at end of file +void patchFonts(NSDictionary *mainFonts, + NSString *fontDefName); \ No newline at end of file diff --git a/Headers/LoaderConfig.h b/Headers/LoaderConfig.h index d15774d..68c8456 100644 --- a/Headers/LoaderConfig.h +++ b/Headers/LoaderConfig.h @@ -1,10 +1,10 @@ #import @interface LoaderConfig : NSObject -@property (nonatomic, assign) BOOL customLoadUrlEnabled; -@property (nonatomic, strong) NSURL *customLoadUrl; +@property(nonatomic, assign) BOOL customLoadUrlEnabled; +@property(nonatomic, strong) NSURL *customLoadUrl; + (instancetype)defaultConfig; + (instancetype)getLoaderConfig; - (instancetype)init; - (BOOL)loadConfig; -@end \ No newline at end of file +@end \ No newline at end of file diff --git a/Headers/RCTCxxBridge.h b/Headers/RCTCxxBridge.h index 47093cc..51134c3 100644 --- a/Headers/RCTCxxBridge.h +++ b/Headers/RCTCxxBridge.h @@ -1,3 +1,5 @@ @interface RCTCxxBridge : NSObject -- (void)executeApplicationScript:(NSData *)script url:(NSURL *)url async:(BOOL)async; -@end \ No newline at end of file +- (void)executeApplicationScript:(NSData *)script + url:(NSURL *)url + async:(BOOL)async; +@end \ No newline at end of file diff --git a/Headers/Theme.h b/Headers/Theme.h index b3be04f..da92361 100644 --- a/Headers/Theme.h +++ b/Headers/Theme.h @@ -1,5 +1,6 @@ #import #import -void swizzleDCDThemeColor(NSDictionary *> *semanticColors); -void swizzleUIColor(NSDictionary *rawColors); \ No newline at end of file +void swizzleDCDThemeColor( + NSDictionary *> *semanticColors); +void swizzleUIColor(NSDictionary *rawColors); \ No newline at end of file diff --git a/Sources/Fonts.x b/Sources/Fonts.x index e3ae4ee..3ca1fef 100644 --- a/Sources/Fonts.x +++ b/Sources/Fonts.x @@ -1,150 +1,187 @@ #import "Fonts.h" -#import "Utils.h" #import "Logger.h" -#import -#import +#import "Utils.h" #import +#import +#import NSMutableDictionary *fontMap; %hook UIFont + (UIFont *)fontWithName:(NSString *)name size:(CGFloat)size { - NSString *replacementName = fontMap[name]; - if (replacementName) { - UIFontDescriptor *replacementDescriptor = [UIFontDescriptor fontDescriptorWithName:replacementName size:size]; - UIFontDescriptor *fallbackDescriptor = [replacementDescriptor fontDescriptorByAddingAttributes:@{UIFontDescriptorNameAttribute: @[name]}]; - UIFontDescriptor *finalDescriptor = [replacementDescriptor fontDescriptorByAddingAttributes:@{UIFontDescriptorCascadeListAttribute: @[fallbackDescriptor]}]; - - return [UIFont fontWithDescriptor:finalDescriptor size:size]; - } - return %orig; + NSString *replacementName = fontMap[name]; + if (replacementName) { + UIFontDescriptor *replacementDescriptor = + [UIFontDescriptor fontDescriptorWithName:replacementName size:size]; + UIFontDescriptor *fallbackDescriptor = + [replacementDescriptor fontDescriptorByAddingAttributes:@{ + UIFontDescriptorNameAttribute : @[ name ] + }]; + UIFontDescriptor *finalDescriptor = + [replacementDescriptor fontDescriptorByAddingAttributes:@{ + UIFontDescriptorCascadeListAttribute : @[ fallbackDescriptor ] + }]; + + return [UIFont fontWithDescriptor:finalDescriptor size:size]; + } + return %orig; } -+ (UIFont *)fontWithDescriptor:(UIFontDescriptor *)descriptor size:(CGFloat)size { - NSString *replacementName = fontMap[descriptor.postscriptName]; - if (replacementName) { - UIFontDescriptor *replacementDescriptor = [UIFontDescriptor fontDescriptorWithName:replacementName size:size]; - UIFontDescriptor *finalDescriptor = [replacementDescriptor fontDescriptorByAddingAttributes:@{UIFontDescriptorCascadeListAttribute: @[descriptor]}]; - - return [UIFont fontWithDescriptor:finalDescriptor size:size]; - } - return %orig; ++ (UIFont *)fontWithDescriptor:(UIFontDescriptor *)descriptor + size:(CGFloat)size { + NSString *replacementName = fontMap[descriptor.postscriptName]; + if (replacementName) { + UIFontDescriptor *replacementDescriptor = + [UIFontDescriptor fontDescriptorWithName:replacementName size:size]; + UIFontDescriptor *finalDescriptor = + [replacementDescriptor fontDescriptorByAddingAttributes:@{ + UIFontDescriptorCascadeListAttribute : @[ descriptor ] + }]; + + return [UIFont fontWithDescriptor:finalDescriptor size:size]; + } + return %orig; } + (UIFont *)systemFontOfSize:(CGFloat)size { - NSString *replacementName = fontMap[@"systemFont"]; - if (replacementName) { - return [UIFont fontWithName:replacementName size:size]; - } - return %orig; + NSString *replacementName = fontMap[@"systemFont"]; + if (replacementName) { + return [UIFont fontWithName:replacementName size:size]; + } + return %orig; } + (UIFont *)preferredFontForTextStyle:(UIFontTextStyle)style { - NSString *replacementName = fontMap[@"systemFont"]; - if (replacementName) { - return [UIFont fontWithName:replacementName size:[UIFont systemFontSize]]; - } - return %orig; + NSString *replacementName = fontMap[@"systemFont"]; + if (replacementName) { + return [UIFont fontWithName:replacementName size:[UIFont systemFontSize]]; + } + return %orig; } %end -void patchFonts(NSDictionary *mainFonts, NSString *fontDefName) { - BunnyLog(@"patchFonts called with fonts: %@ and def name: %@", mainFonts, fontDefName); - - if (!fontMap) { - BunnyLog(@"Creating new fontMap"); - fontMap = [NSMutableDictionary dictionary]; +void patchFonts(NSDictionary *mainFonts, + NSString *fontDefName) { + BunnyLog(@"patchFonts called with fonts: %@ and def name: %@", mainFonts, + fontDefName); + + if (!fontMap) { + BunnyLog(@"Creating new fontMap"); + fontMap = [NSMutableDictionary dictionary]; + } + + NSString *fontJson = [NSString + stringWithContentsOfURL:[getPyoncordDirectory() + URLByAppendingPathComponent:@"fonts.json"] + encoding:NSUTF8StringEncoding + error:nil]; + if (fontJson) { + BunnyLog(@"Found existing fonts.json: %@", fontJson); + } + + for (NSString *fontName in mainFonts) { + NSString *url = mainFonts[fontName]; + BunnyLog(@"Replacing font %@ with URL: %@", fontName, url); + + NSURL *fontURL = [NSURL URLWithString:url]; + NSString *fontExtension = fontURL.pathExtension; + + NSURL *fontCachePath = [[[getPyoncordDirectory() + URLByAppendingPathComponent:@"downloads" + isDirectory:YES] URLByAppendingPathComponent:@"fonts" + isDirectory:YES] + URLByAppendingPathComponent:fontDefName + isDirectory:YES]; + + fontCachePath = [fontCachePath + URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", + fontName, + fontExtension]]; + + NSURL *parentDir = [fontCachePath URLByDeletingLastPathComponent]; + if (![[NSFileManager defaultManager] fileExistsAtPath:parentDir.path]) { + BunnyLog(@"Creating parent directory: %@", parentDir.path); + [[NSFileManager defaultManager] createDirectoryAtURL:parentDir + withIntermediateDirectories:YES + attributes:nil + error:nil]; } - - NSString *fontJson = [NSString stringWithContentsOfURL:[getPyoncordDirectory() URLByAppendingPathComponent:@"fonts.json"] - encoding:NSUTF8StringEncoding - error:nil]; - if (fontJson) { - BunnyLog(@"Found existing fonts.json: %@", fontJson); + + if (![[NSFileManager defaultManager] fileExistsAtPath:fontCachePath.path]) { + BunnyLog(@"Downloading font %@ from %@", fontName, url); + NSData *data = [NSData dataWithContentsOfURL:fontURL]; + if (data) { + BunnyLog(@"Writing font data to: %@", fontCachePath.path); + [data writeToURL:fontCachePath atomically:YES]; + } } - - for (NSString *fontName in mainFonts) { - NSString *url = mainFonts[fontName]; - BunnyLog(@"Replacing font %@ with URL: %@", fontName, url); - - NSURL *fontURL = [NSURL URLWithString:url]; - NSString *fontExtension = fontURL.pathExtension; - - NSURL *fontCachePath = [[[getPyoncordDirectory() - URLByAppendingPathComponent:@"downloads" isDirectory:YES] - URLByAppendingPathComponent:@"fonts" isDirectory:YES] - URLByAppendingPathComponent:fontDefName isDirectory:YES]; - - fontCachePath = [fontCachePath URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", fontName, fontExtension]]; - - NSURL *parentDir = [fontCachePath URLByDeletingLastPathComponent]; - if (![[NSFileManager defaultManager] fileExistsAtPath:parentDir.path]) { - BunnyLog(@"Creating parent directory: %@", parentDir.path); - [[NSFileManager defaultManager] createDirectoryAtURL:parentDir - withIntermediateDirectories:YES - attributes:nil - error:nil]; - } - - if (![[NSFileManager defaultManager] fileExistsAtPath:fontCachePath.path]) { - BunnyLog(@"Downloading font %@ from %@", fontName, url); - NSData *data = [NSData dataWithContentsOfURL:fontURL]; - if (data) { - BunnyLog(@"Writing font data to: %@", fontCachePath.path); - [data writeToURL:fontCachePath atomically:YES]; - } + + NSData *fontData = [NSData dataWithContentsOfURL:fontCachePath]; + if (fontData) { + BunnyLog(@"Registering font %@ with provider", fontName); + CGDataProviderRef provider = + CGDataProviderCreateWithCFData((__bridge CFDataRef)fontData); + CGFontRef font = CGFontCreateWithDataProvider(provider); + + if (font) { + CFStringRef postScriptName = CGFontCopyPostScriptName(font); + + CTFontRef existingFont = CTFontCreateWithName(postScriptName, 0, NULL); + if (existingFont) { + CFErrorRef unregisterError = NULL; + if (!CTFontManagerUnregisterGraphicsFont(font, &unregisterError)) { + BunnyLog(@"Failed to deregister font %@: %@", + (__bridge NSString *)postScriptName, + unregisterError + ? (__bridge NSString *)CFErrorCopyDescription( + unregisterError) + : @"Unknown error"); + if (unregisterError) + CFRelease(unregisterError); + } + CFRelease(existingFont); } - - NSData *fontData = [NSData dataWithContentsOfURL:fontCachePath]; - if (fontData) { - BunnyLog(@"Registering font %@ with provider", fontName); - CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)fontData); - CGFontRef font = CGFontCreateWithDataProvider(provider); - - if (font) { - CFStringRef postScriptName = CGFontCopyPostScriptName(font); - - CTFontRef existingFont = CTFontCreateWithName(postScriptName, 0, NULL); - if (existingFont) { - CFErrorRef unregisterError = NULL; - if (!CTFontManagerUnregisterGraphicsFont(font, &unregisterError)) { - BunnyLog(@"Failed to deregister font %@: %@", (__bridge NSString *)postScriptName, - unregisterError ? (__bridge NSString *)CFErrorCopyDescription(unregisterError) : @"Unknown error"); - if (unregisterError) CFRelease(unregisterError); - } - CFRelease(existingFont); - } - - CFErrorRef error = NULL; - if (CTFontManagerRegisterGraphicsFont(font, &error)) { - fontMap[fontName] = (__bridge NSString *)postScriptName; - BunnyLog(@"Successfully registered font %@ to %@", fontName, (__bridge NSString *)postScriptName); - - NSError *jsonError; - NSData *jsonData = [NSJSONSerialization dataWithJSONObject:fontMap options:0 error:&jsonError]; - if (!jsonError) { - [jsonData writeToURL:[getPyoncordDirectory() URLByAppendingPathComponent:@"fontMap.json"] atomically:YES]; - } - } else { - NSString *errorDesc = error ? (__bridge NSString *)CFErrorCopyDescription(error) : @"Unknown error"; - BunnyLog(@"Failed to register font %@: %@", fontName, errorDesc); - if (error) CFRelease(error); - } - - CFRelease(postScriptName); - CFRelease(font); - } - CGDataProviderRelease(provider); + + CFErrorRef error = NULL; + if (CTFontManagerRegisterGraphicsFont(font, &error)) { + fontMap[fontName] = (__bridge NSString *)postScriptName; + BunnyLog(@"Successfully registered font %@ to %@", fontName, + (__bridge NSString *)postScriptName); + + NSError *jsonError; + NSData *jsonData = + [NSJSONSerialization dataWithJSONObject:fontMap + options:0 + error:&jsonError]; + if (!jsonError) { + [jsonData + writeToURL:[getPyoncordDirectory() + URLByAppendingPathComponent:@"fontMap.json"] + atomically:YES]; + } + } else { + NSString *errorDesc = + error ? (__bridge NSString *)CFErrorCopyDescription(error) + : @"Unknown error"; + BunnyLog(@"Failed to register font %@: %@", fontName, errorDesc); + if (error) + CFRelease(error); } + + CFRelease(postScriptName); + CFRelease(font); + } + CGDataProviderRelease(provider); } -} + } +} %ctor { - @autoreleasepool { - fontMap = [NSMutableDictionary dictionary]; - BunnyLog(@"Font hooks initialized"); - %init; - } -} \ No newline at end of file + @autoreleasepool { + fontMap = [NSMutableDictionary dictionary]; + BunnyLog(@"Font hooks initialized"); + %init; + } +} diff --git a/Sources/LoaderConfig.m b/Sources/LoaderConfig.m index bfd6794..693764d 100644 --- a/Sources/LoaderConfig.m +++ b/Sources/LoaderConfig.m @@ -1,99 +1,111 @@ #import "LoaderConfig.h" -#import "Utils.h" #import "Logger.h" +#import "Utils.h" @implementation LoaderConfig - (instancetype)init { - self = [super init]; - if (self) { - self.customLoadUrlEnabled = NO; - self.customLoadUrl = [NSURL URLWithString:@"http://localhost:4040/bunny.js"]; - } - return self; + self = [super init]; + if (self) { + self.customLoadUrlEnabled = NO; + self.customLoadUrl = + [NSURL URLWithString:@"http://localhost:4040/bunny.js"]; + } + return self; } - (BOOL)loadConfig { - NSURL *loaderConfigUrl = [getPyoncordDirectory() URLByAppendingPathComponent:@"loader.json"]; - BunnyLog(@"Attempting to load config from: %@", loaderConfigUrl.path); - - if ([[NSFileManager defaultManager] fileExistsAtPath:loaderConfigUrl.path]) { - NSError *error = nil; - NSData *data = [NSData dataWithContentsOfURL:loaderConfigUrl]; - NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; - - if (error) { - BunnyLog(@"Error parsing loader config: %@", error); - return NO; - } - - if (json) { - NSDictionary *customLoadUrl = json[@"customLoadUrl"]; - if (customLoadUrl) { - self.customLoadUrlEnabled = [customLoadUrl[@"enabled"] boolValue]; - NSString *urlString = customLoadUrl[@"url"]; - if (urlString) { - self.customLoadUrl = [NSURL URLWithString:urlString]; - } - } - - BunnyLog(@"Loader config loaded - Custom URL %@: %@", - self.customLoadUrlEnabled ? @"enabled" : @"disabled", - self.customLoadUrl.absoluteString); - return YES; + NSURL *loaderConfigUrl = + [getPyoncordDirectory() URLByAppendingPathComponent:@"loader.json"]; + BunnyLog(@"Attempting to load config from: %@", loaderConfigUrl.path); + + if ([[NSFileManager defaultManager] fileExistsAtPath:loaderConfigUrl.path]) { + NSError *error = nil; + NSData *data = [NSData dataWithContentsOfURL:loaderConfigUrl]; + NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data + options:0 + error:&error]; + + if (error) { + BunnyLog(@"Error parsing loader config: %@", error); + return NO; + } + + if (json) { + NSDictionary *customLoadUrl = json[@"customLoadUrl"]; + if (customLoadUrl) { + self.customLoadUrlEnabled = [customLoadUrl[@"enabled"] boolValue]; + NSString *urlString = customLoadUrl[@"url"]; + if (urlString) { + self.customLoadUrl = [NSURL URLWithString:urlString]; } + } + + BunnyLog(@"Loader config loaded - Custom URL %@: %@", + self.customLoadUrlEnabled ? @"enabled" : @"disabled", + self.customLoadUrl.absoluteString); + return YES; } - - BunnyLog(@"Using default loader config: %@", self.customLoadUrl.absoluteString); - return NO; + } + + BunnyLog(@"Using default loader config: %@", + self.customLoadUrl.absoluteString); + return NO; } + (instancetype)defaultConfig { - LoaderConfig *config = [[LoaderConfig alloc] init]; - config.customLoadUrlEnabled = NO; - config.customLoadUrl = [NSURL URLWithString:@"http://localhost:4040/bunny.js"]; - return config; + LoaderConfig *config = [[LoaderConfig alloc] init]; + config.customLoadUrlEnabled = NO; + config.customLoadUrl = + [NSURL URLWithString:@"http://localhost:4040/bunny.js"]; + return config; } + (instancetype)getLoaderConfig { - BunnyLog(@"Getting loader config"); - - NSURL *loaderConfigUrl = [getPyoncordDirectory() URLByAppendingPathComponent:@"loader.json"]; - - if ([[NSFileManager defaultManager] fileExistsAtPath:loaderConfigUrl.path]) { - NSError *error = nil; - NSData *data = [NSData dataWithContentsOfURL:loaderConfigUrl]; - NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; - - if (json && !error) { - LoaderConfig *config = [[LoaderConfig alloc] init]; - NSDictionary *customLoadUrl = json[@"customLoadUrl"]; - if (customLoadUrl) { - config.customLoadUrlEnabled = [customLoadUrl[@"enabled"] boolValue]; - NSString *urlString = customLoadUrl[@"url"]; - if (urlString) { - config.customLoadUrl = [NSURL URLWithString:urlString]; - } - } - return config; + BunnyLog(@"Getting loader config"); + + NSURL *loaderConfigUrl = + [getPyoncordDirectory() URLByAppendingPathComponent:@"loader.json"]; + + if ([[NSFileManager defaultManager] fileExistsAtPath:loaderConfigUrl.path]) { + NSError *error = nil; + NSData *data = [NSData dataWithContentsOfURL:loaderConfigUrl]; + NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data + options:0 + error:&error]; + + if (json && !error) { + LoaderConfig *config = [[LoaderConfig alloc] init]; + NSDictionary *customLoadUrl = json[@"customLoadUrl"]; + if (customLoadUrl) { + config.customLoadUrlEnabled = [customLoadUrl[@"enabled"] boolValue]; + NSString *urlString = customLoadUrl[@"url"]; + if (urlString) { + config.customLoadUrl = [NSURL URLWithString:urlString]; } + } + return config; } - - BunnyLog(@"Couldn't get loader config"); - return [LoaderConfig defaultConfig]; + } + + BunnyLog(@"Couldn't get loader config"); + return [LoaderConfig defaultConfig]; } - (BOOL)saveConfig { - NSURL *loaderConfigUrl = [getPyoncordDirectory() URLByAppendingPathComponent:@"loader.json"]; - NSDictionary *json = @{ - @"customLoadUrl": @{ - @"enabled": @(self.customLoadUrlEnabled), - @"url": self.customLoadUrl.absoluteString - } - }; - - NSData *data = [NSJSONSerialization dataWithJSONObject:json options:0 error:nil]; - return [data writeToURL:loaderConfigUrl atomically:YES]; + NSURL *loaderConfigUrl = + [getPyoncordDirectory() URLByAppendingPathComponent:@"loader.json"]; + NSDictionary *json = @{ + @"customLoadUrl" : @{ + @"enabled" : @(self.customLoadUrlEnabled), + @"url" : self.customLoadUrl.absoluteString + } + }; + + NSData *data = [NSJSONSerialization dataWithJSONObject:json + options:0 + error:nil]; + return [data writeToURL:loaderConfigUrl atomically:YES]; } -@end \ No newline at end of file +@end \ No newline at end of file diff --git a/Sources/Sideloading.x b/Sources/Sideloading.x index 9d08c24..a6fb206 100644 --- a/Sources/Sideloading.x +++ b/Sources/Sideloading.x @@ -1,88 +1,103 @@ +#import "Logger.h" #import #import #import -#import "Logger.h" #define DISCORD_BUNDLE_ID @"com.hammerandchisel.discord" #define DISCORD_NAME @"Discord" static NSString *getAccessGroupID(void) { - NSDictionary *query = @{ - (__bridge NSString *)kSecClass: (__bridge NSString *)kSecClassGenericPassword, - (__bridge NSString *)kSecAttrAccount: @"bundleSeedID", - (__bridge NSString *)kSecAttrService: @"", - (__bridge NSString *)kSecReturnAttributes: @YES - }; - - CFDictionaryRef result = NULL; - OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result); - - if (status == errSecItemNotFound) { - status = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef *)&result); - } - - if (status != errSecSuccess) return nil; - - NSString *accessGroup = [(__bridge NSDictionary *)result objectForKey:(__bridge NSString *)kSecAttrAccessGroup]; - if (result) CFRelease(result); - - return accessGroup; + NSDictionary *query = @{ + (__bridge NSString *) + kSecClass : (__bridge NSString *)kSecClassGenericPassword, + (__bridge NSString *)kSecAttrAccount : @"bundleSeedID", + (__bridge NSString *)kSecAttrService : @"", + (__bridge NSString *)kSecReturnAttributes : @YES + }; + + CFDictionaryRef result = NULL; + OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, + (CFTypeRef *)&result); + + if (status == errSecItemNotFound) { + status = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef *)&result); + } + + if (status != errSecSuccess) + return nil; + + NSString *accessGroup = [(__bridge NSDictionary *)result + objectForKey:(__bridge NSString *)kSecAttrAccessGroup]; + if (result) + CFRelease(result); + + return accessGroup; } static BOOL isSelfCall(void) { - NSArray *address = [NSThread callStackReturnAddresses]; - Dl_info info = {0}; - if (dladdr((void *)[address[2] longLongValue], &info) == 0) return NO; - NSString *path = [NSString stringWithUTF8String:info.dli_fname]; - return [path hasPrefix:NSBundle.mainBundle.bundlePath]; + NSArray *address = [NSThread callStackReturnAddresses]; + Dl_info info = {0}; + if (dladdr((void *)[address[2] longLongValue], &info) == 0) + return NO; + NSString *path = [NSString stringWithUTF8String:info.dli_fname]; + return [path hasPrefix:NSBundle.mainBundle.bundlePath]; } %group Sideloading %hook NSBundle - (NSString *)bundleIdentifier { - return isSelfCall() ? DISCORD_BUNDLE_ID : %orig; + return isSelfCall() ? DISCORD_BUNDLE_ID : %orig; } - (NSDictionary *)infoDictionary { - if (!isSelfCall()) return %orig; - - NSMutableDictionary *info = [%orig mutableCopy]; - info[@"CFBundleIdentifier"] = DISCORD_BUNDLE_ID; - info[@"CFBundleDisplayName"] = DISCORD_NAME; - info[@"CFBundleName"] = DISCORD_NAME; - return info; + if (!isSelfCall()) + return %orig; + + NSMutableDictionary *info = [%orig mutableCopy]; + info[@"CFBundleIdentifier"] = DISCORD_BUNDLE_ID; + info[@"CFBundleDisplayName"] = DISCORD_NAME; + info[@"CFBundleName"] = DISCORD_NAME; + return info; } - (id)objectForInfoDictionaryKey:(NSString *)key { - if (!isSelfCall()) return %orig; - - if ([key isEqualToString:@"CFBundleIdentifier"]) return DISCORD_BUNDLE_ID; - if ([key isEqualToString:@"CFBundleDisplayName"] || - [key isEqualToString:@"CFBundleName"]) return DISCORD_NAME; + if (!isSelfCall()) return %orig; + + if ([key isEqualToString:@"CFBundleIdentifier"]) + return DISCORD_BUNDLE_ID; + if ([key isEqualToString:@"CFBundleDisplayName"] || + [key isEqualToString:@"CFBundleName"]) + return DISCORD_NAME; + return %orig; } %end %hook NSFileManager -- (NSURL *)containerURLForSecurityApplicationGroupIdentifier:(NSString *)groupIdentifier { - BunnyLog(@"containerURLForSecurityApplicationGroupIdentifier called! %@", groupIdentifier ?: @"nil"); - - NSArray *paths = [self URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask]; - NSURL *lastPath = [paths lastObject]; - return [lastPath URLByAppendingPathComponent:@"AppGroup"]; +- (NSURL *)containerURLForSecurityApplicationGroupIdentifier: + (NSString *)groupIdentifier { + BunnyLog(@"containerURLForSecurityApplicationGroupIdentifier called! %@", + groupIdentifier ?: @"nil"); + + NSArray *paths = [self URLsForDirectory:NSDocumentDirectory + inDomains:NSUserDomainMask]; + NSURL *lastPath = [paths lastObject]; + return [lastPath URLByAppendingPathComponent:@"AppGroup"]; } %end %hook UIPasteboard - (NSString *)_accessGroup { - return getAccessGroupID(); + return getAccessGroupID(); } %end %end %ctor { - BOOL isAppStoreApp = [[NSFileManager defaultManager] fileExistsAtPath:[[NSBundle mainBundle] appStoreReceiptURL].path]; - if (!isAppStoreApp) %init(Sideloading); -} \ No newline at end of file + BOOL isAppStoreApp = [[NSFileManager defaultManager] + fileExistsAtPath:[[NSBundle mainBundle] appStoreReceiptURL].path]; + if (!isAppStoreApp) + %init(Sideloading); +} diff --git a/Sources/Theme.m b/Sources/Theme.m index a31c654..ca4f792 100644 --- a/Sources/Theme.m +++ b/Sources/Theme.m @@ -1,82 +1,84 @@ #import "Theme.h" -#import "Utils.h" #import "Logger.h" +#import "Utils.h" #import -void swizzleDCDThemeColor(NSDictionary *> *semanticColors) { - BunnyLog(@"Swizzling DCDThemeColor"); - - Class DCDTheme = NSClassFromString(@"DCDTheme"); - Class dcdThemeTarget = object_getClass(DCDTheme); - - SEL themeIndexSelector = NSSelectorFromString(@"themeIndex"); - Method themeIndexMethod = class_getClassMethod(dcdThemeTarget, themeIndexSelector); - IMP themeIndexImpl = method_getImplementation(themeIndexMethod); - int (*themeIndex)(id, SEL) = (int (*)(id, SEL))themeIndexImpl; - - Class DCDThemeColor = NSClassFromString(@"DCDThemeColor"); - Class target = object_getClass(DCDThemeColor); - - unsigned int methodCount; - Method *methods = class_copyMethodList(target, &methodCount); - - for (unsigned int i = 0; i < methodCount; i++) { - Method method = methods[i]; - SEL selector = method_getName(method); - NSString *methodName = NSStringFromSelector(selector); - - NSArray *semanticColor = semanticColors[methodName]; - if (semanticColor) { - BunnyLog(@"Swizzling %@", methodName); - - IMP originalImpl = method_getImplementation(method); - UIColor *(*original)(id, SEL) = (UIColor *(*)(id, SEL))originalImpl; - - id block = ^UIColor *(id self) { - int themeIndexVal = themeIndex(dcdThemeTarget, themeIndexSelector); - if (semanticColor.count - 1 >= themeIndexVal) { - UIColor *semanticUIColor = hexToUIColor(semanticColor[themeIndexVal]); - if (semanticUIColor) { - return semanticUIColor; - } - } - return original(target, selector); - }; - - IMP newImpl = imp_implementationWithBlock(block); - method_setImplementation(method, newImpl); +void swizzleDCDThemeColor( + NSDictionary *> *semanticColors) { + BunnyLog(@"Swizzling DCDThemeColor"); + + Class DCDTheme = NSClassFromString(@"DCDTheme"); + Class dcdThemeTarget = object_getClass(DCDTheme); + + SEL themeIndexSelector = NSSelectorFromString(@"themeIndex"); + Method themeIndexMethod = + class_getClassMethod(dcdThemeTarget, themeIndexSelector); + IMP themeIndexImpl = method_getImplementation(themeIndexMethod); + int (*themeIndex)(id, SEL) = (int (*)(id, SEL))themeIndexImpl; + + Class DCDThemeColor = NSClassFromString(@"DCDThemeColor"); + Class target = object_getClass(DCDThemeColor); + + unsigned int methodCount; + Method *methods = class_copyMethodList(target, &methodCount); + + for (unsigned int i = 0; i < methodCount; i++) { + Method method = methods[i]; + SEL selector = method_getName(method); + NSString *methodName = NSStringFromSelector(selector); + + NSArray *semanticColor = semanticColors[methodName]; + if (semanticColor) { + BunnyLog(@"Swizzling %@", methodName); + + IMP originalImpl = method_getImplementation(method); + UIColor *(*original)(id, SEL) = (UIColor * (*)(id, SEL)) originalImpl; + + id block = ^UIColor *(id self) { + int themeIndexVal = themeIndex(dcdThemeTarget, themeIndexSelector); + if (semanticColor.count - 1 >= themeIndexVal) { + UIColor *semanticUIColor = hexToUIColor(semanticColor[themeIndexVal]); + if (semanticUIColor) { + return semanticUIColor; + } } + return original(target, selector); + }; + + IMP newImpl = imp_implementationWithBlock(block); + method_setImplementation(method, newImpl); } - - free(methods); + } + + free(methods); } void swizzleUIColor(NSDictionary *rawColors) { - BunnyLog(@"Swizzling UIColor"); - - Class UIColorClass = NSClassFromString(@"UIColor"); - Class target = object_getClass(UIColorClass); - - unsigned int methodCount; - Method *methods = class_copyMethodList(target, &methodCount); - - for (unsigned int i = 0; i < methodCount; i++) { - Method method = methods[i]; - SEL selector = method_getName(method); - NSString *methodName = NSStringFromSelector(selector); - - NSString *rawColor = rawColors[methodName]; - if (rawColor) { - BunnyLog(@"Swizzling %@", methodName); - - id block = ^UIColor *(id self) { - return hexToUIColor(rawColor); - }; - - IMP newImpl = imp_implementationWithBlock(block); - method_setImplementation(method, newImpl); - } + BunnyLog(@"Swizzling UIColor"); + + Class UIColorClass = NSClassFromString(@"UIColor"); + Class target = object_getClass(UIColorClass); + + unsigned int methodCount; + Method *methods = class_copyMethodList(target, &methodCount); + + for (unsigned int i = 0; i < methodCount; i++) { + Method method = methods[i]; + SEL selector = method_getName(method); + NSString *methodName = NSStringFromSelector(selector); + + NSString *rawColor = rawColors[methodName]; + if (rawColor) { + BunnyLog(@"Swizzling %@", methodName); + + id block = ^UIColor *(id self) { + return hexToUIColor(rawColor); + }; + + IMP newImpl = imp_implementationWithBlock(block); + method_setImplementation(method, newImpl); } - - free(methods); -} \ No newline at end of file + } + + free(methods); +} \ No newline at end of file diff --git a/Sources/Tweak.x b/Sources/Tweak.x index 2fa7468..ed79a0a 100644 --- a/Sources/Tweak.x +++ b/Sources/Tweak.x @@ -1,10 +1,10 @@ -#import -#import -#import "Utils.h" -#import "Logger.h" -#import "Theme.h" #import "Fonts.h" #import "LoaderConfig.h" +#import "Logger.h" +#import "Theme.h" +#import "Utils.h" +#import +#import static NSURL *source; static BOOL isJailbroken; @@ -14,158 +14,203 @@ static LoaderConfig *loaderConfig; %hook RCTCxxBridge -- (void)executeApplicationScript:(NSData *)script url:(NSURL *)url async:(BOOL)async { - if (![url.absoluteString containsString:@"main.jsbundle"]) { - return %orig; - } - - NSBundle *bunnyPatchesBundle = [NSBundle bundleWithPath:bunnyPatchesBundlePath]; - if (!bunnyPatchesBundle) { - BunnyLog(@"Failed to load BunnyPatches bundle from path: %@", bunnyPatchesBundlePath); - showErrorAlert(@"Loader Error", @"Failed to initialize mod loader. Please reinstall the tweak."); - return %orig; - } - - NSURL *patchPath = [bunnyPatchesBundle URLForResource:@"payload-base" withExtension:@"js"]; - if (!patchPath) { - BunnyLog(@"Failed to find payload-base.js in bundle"); - showErrorAlert(@"Loader Error", @"Failed to initialize mod loader. Please reinstall the tweak."); - return %orig; - } - - NSData *patchData = [NSData dataWithContentsOfURL:patchPath]; - BunnyLog(@"Injecting loader"); - %orig(patchData, source, YES); - - __block NSData *bundle = [NSData dataWithContentsOfURL:[pyoncordDirectory URLByAppendingPathComponent:@"bundle.js"]]; - - dispatch_group_t group = dispatch_group_create(); - dispatch_group_enter(group); - - NSURL *bundleUrl; - if (loaderConfig.customLoadUrlEnabled && loaderConfig.customLoadUrl) { - bundleUrl = loaderConfig.customLoadUrl; - BunnyLog(@"Using custom load URL: %@", bundleUrl.absoluteString); - } else { - bundleUrl = [NSURL URLWithString:@"https://raw.githubusercontent.com/bunny-mod/builds/main/bunny.min.js"]; - BunnyLog(@"Using default bundle URL: %@", bundleUrl.absoluteString); - } - - NSMutableURLRequest *bundleRequest = [NSMutableURLRequest requestWithURL:bundleUrl - cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData - timeoutInterval:3.0]; - - NSString *bundleEtag = [NSString stringWithContentsOfURL:[pyoncordDirectory URLByAppendingPathComponent:@"etag.txt"] - encoding:NSUTF8StringEncoding - error:nil]; - if (bundleEtag && bundle) { - [bundleRequest setValue:bundleEtag forHTTPHeaderField:@"If-None-Match"]; - } - - NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; - [[session dataTaskWithRequest:bundleRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - if ([response isKindOfClass:[NSHTTPURLResponse class]]) { +- (void)executeApplicationScript:(NSData *)script + url:(NSURL *)url + async:(BOOL)async { + if (![url.absoluteString containsString:@"main.jsbundle"]) { + return %orig; + } + + NSBundle *bunnyPatchesBundle = + [NSBundle bundleWithPath:bunnyPatchesBundlePath]; + if (!bunnyPatchesBundle) { + BunnyLog(@"Failed to load BunnyPatches bundle from path: %@", + bunnyPatchesBundlePath); + showErrorAlert( + @"Loader Error", + @"Failed to initialize mod loader. Please reinstall the tweak."); + return %orig; + } + + NSURL *patchPath = [bunnyPatchesBundle URLForResource:@"payload-base" + withExtension:@"js"]; + if (!patchPath) { + BunnyLog(@"Failed to find payload-base.js in bundle"); + showErrorAlert( + @"Loader Error", + @"Failed to initialize mod loader. Please reinstall the tweak."); + return %orig; + } + + NSData *patchData = [NSData dataWithContentsOfURL:patchPath]; + BunnyLog(@"Injecting loader"); + %orig(patchData, source, YES); + + __block NSData *bundle = [NSData + dataWithContentsOfURL:[pyoncordDirectory + URLByAppendingPathComponent:@"bundle.js"]]; + + dispatch_group_t group = dispatch_group_create(); + dispatch_group_enter(group); + + NSURL *bundleUrl; + if (loaderConfig.customLoadUrlEnabled && loaderConfig.customLoadUrl) { + bundleUrl = loaderConfig.customLoadUrl; + BunnyLog(@"Using custom load URL: %@", bundleUrl.absoluteString); + } else { + bundleUrl = [NSURL URLWithString:@"https://raw.githubusercontent.com/" + @"bunny-mod/builds/main/bunny.min.js"]; + BunnyLog(@"Using default bundle URL: %@", bundleUrl.absoluteString); + } + + NSMutableURLRequest *bundleRequest = [NSMutableURLRequest + requestWithURL:bundleUrl + cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData + timeoutInterval:3.0]; + + NSString *bundleEtag = [NSString + stringWithContentsOfURL:[pyoncordDirectory + URLByAppendingPathComponent:@"etag.txt"] + encoding:NSUTF8StringEncoding + error:nil]; + if (bundleEtag && bundle) { + [bundleRequest setValue:bundleEtag forHTTPHeaderField:@"If-None-Match"]; + } + + NSURLSession *session = + [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration + defaultSessionConfiguration]]; + [[session + dataTaskWithRequest:bundleRequest + completionHandler:^(NSData *data, NSURLResponse *response, + NSError *error) { + if ([response isKindOfClass:[NSHTTPURLResponse class]]) { NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; if (httpResponse.statusCode == 200) { - bundle = data; - [bundle writeToURL:[pyoncordDirectory URLByAppendingPathComponent:@"bundle.js"] atomically:YES]; - - NSString *etag = [httpResponse.allHeaderFields objectForKey:@"Etag"]; - if (etag) { - [etag writeToURL:[pyoncordDirectory URLByAppendingPathComponent:@"etag.txt"] - atomically:YES - encoding:NSUTF8StringEncoding - error:nil]; - } + bundle = data; + [bundle writeToURL:[pyoncordDirectory + URLByAppendingPathComponent:@"bundle.js"] + atomically:YES]; + + NSString *etag = + [httpResponse.allHeaderFields objectForKey:@"Etag"]; + if (etag) { + [etag writeToURL:[pyoncordDirectory + URLByAppendingPathComponent:@"etag.txt"] + atomically:YES + encoding:NSUTF8StringEncoding + error:nil]; + } } - } - dispatch_group_leave(group); - }] resume]; - - dispatch_group_wait(group, DISPATCH_TIME_FOREVER); - - NSString *themeString = [NSString stringWithContentsOfURL:[pyoncordDirectory URLByAppendingPathComponent:@"current-theme.json"] - encoding:NSUTF8StringEncoding - error:nil]; - if (themeString) { - NSString *jsCode = [NSString stringWithFormat:@"globalThis.__PYON_LOADER__.storedTheme=%@", themeString]; - %orig([jsCode dataUsingEncoding:NSUTF8StringEncoding], source, async); - } - - NSData *fontData = [NSData dataWithContentsOfURL:[pyoncordDirectory URLByAppendingPathComponent:@"fonts.json"]]; - if (fontData) { - NSError *jsonError; - NSDictionary *fontDict = [NSJSONSerialization JSONObjectWithData:fontData options:0 error:&jsonError]; - if (!jsonError && fontDict[@"main"]) { - BunnyLog(@"Found font configuration, applying..."); - patchFonts(fontDict[@"main"], fontDict[@"name"]); - } + } + dispatch_group_leave(group); + }] resume]; + + dispatch_group_wait(group, DISPATCH_TIME_FOREVER); + + NSString *themeString = [NSString + stringWithContentsOfURL: + [pyoncordDirectory URLByAppendingPathComponent:@"current-theme.json"] + encoding:NSUTF8StringEncoding + error:nil]; + if (themeString) { + NSString *jsCode = + [NSString stringWithFormat:@"globalThis.__PYON_LOADER__.storedTheme=%@", + themeString]; + %orig([jsCode dataUsingEncoding:NSUTF8StringEncoding], source, + async); + } + + NSData *fontData = [NSData + dataWithContentsOfURL:[pyoncordDirectory + URLByAppendingPathComponent:@"fonts.json"]]; + if (fontData) { + NSError *jsonError; + NSDictionary *fontDict = + [NSJSONSerialization JSONObjectWithData:fontData + options:0 + error:&jsonError]; + if (!jsonError && fontDict[@"main"]) { + BunnyLog(@"Found font configuration, applying..."); + patchFonts(fontDict[@"main"], fontDict[@"name"]); } - - if (bundle) { - BunnyLog(@"Executing JS bundle"); - %orig(bundle, source, async); - } - - NSURL *preloadsDirectory = [pyoncordDirectory URLByAppendingPathComponent:@"preloads"]; - if ([[NSFileManager defaultManager] fileExistsAtPath:preloadsDirectory.path]) { - NSError *error = nil; - NSArray *contents = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:preloadsDirectory - includingPropertiesForKeys:nil - options:0 - error:&error]; - if (!error) { - for (NSURL *fileURL in contents) { - if ([[fileURL pathExtension] isEqualToString:@"js"]) { - BunnyLog(@"Executing preload JS file %@", fileURL.absoluteString); - NSData *data = [NSData dataWithContentsOfURL:fileURL]; - if (data) { - %orig(data, source, async); - } - } - } - } else { - BunnyLog(@"Error reading contents of preloads directory"); + } + + if (bundle) { + BunnyLog(@"Executing JS bundle"); + %orig(bundle, source, async); + } + + NSURL *preloadsDirectory = + [pyoncordDirectory URLByAppendingPathComponent:@"preloads"]; + if ([[NSFileManager defaultManager] + fileExistsAtPath:preloadsDirectory.path]) { + NSError *error = nil; + NSArray *contents = [[NSFileManager defaultManager] + contentsOfDirectoryAtURL:preloadsDirectory + includingPropertiesForKeys:nil + options:0 + error:&error]; + if (!error) { + for (NSURL *fileURL in contents) { + if ([[fileURL pathExtension] isEqualToString:@"js"]) { + BunnyLog(@"Executing preload JS file %@", fileURL.absoluteString); + NSData *data = [NSData dataWithContentsOfURL:fileURL]; + if (data) { + %orig(data, source, async); + } } + } + } else { + BunnyLog(@"Error reading contents of preloads directory"); } + } - %orig(script, url, async); + %orig(script, url, async); } %end %ctor { - @autoreleasepool { - source = [NSURL URLWithString:@"bunny"]; - - NSString *install_prefix = @"/var/jb"; - isJailbroken = [[NSFileManager defaultManager] fileExistsAtPath:install_prefix]; - - NSString *bundlePath = [NSString stringWithFormat:@"%@/Library/Application Support/BunnyResources.bundle", install_prefix]; - BunnyLog(@"Is jailbroken: %d", isJailbroken); - BunnyLog(@"Bundle path for jailbroken: %@", bundlePath); - - NSString *jailedPath = [[NSBundle mainBundle].bundleURL.path stringByAppendingPathComponent:@"BunnyResources.bundle"]; - BunnyLog(@"Bundle path for jailed: %@", jailedPath); - - bunnyPatchesBundlePath = isJailbroken ? bundlePath : jailedPath; - BunnyLog(@"Selected bundle path: %@", bunnyPatchesBundlePath); - - BOOL bundleExists = [[NSFileManager defaultManager] fileExistsAtPath:bunnyPatchesBundlePath]; - BunnyLog(@"Bundle exists at path: %d", bundleExists); - - NSError *error = nil; - NSArray *bundleContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:bunnyPatchesBundlePath error:&error]; - if (error) { - BunnyLog(@"Error listing bundle contents: %@", error); - } else { - BunnyLog(@"Bundle contents: %@", bundleContents); - } - -pyoncordDirectory = getPyoncordDirectory(); - loaderConfig = [[LoaderConfig alloc] init]; - [loaderConfig loadConfig]; - - %init; + @autoreleasepool { + source = [NSURL URLWithString:@"bunny"]; + + NSString *install_prefix = @"/var/jb"; + isJailbroken = + [[NSFileManager defaultManager] fileExistsAtPath:install_prefix]; + + NSString *bundlePath = + [NSString stringWithFormat: + @"%@/Library/Application Support/BunnyResources.bundle", + install_prefix]; + BunnyLog(@"Is jailbroken: %d", isJailbroken); + BunnyLog(@"Bundle path for jailbroken: %@", bundlePath); + + NSString *jailedPath = [[NSBundle mainBundle].bundleURL.path + stringByAppendingPathComponent:@"BunnyResources.bundle"]; + BunnyLog(@"Bundle path for jailed: %@", jailedPath); + + bunnyPatchesBundlePath = isJailbroken ? bundlePath : jailedPath; + BunnyLog(@"Selected bundle path: %@", bunnyPatchesBundlePath); + + BOOL bundleExists = [[NSFileManager defaultManager] + fileExistsAtPath:bunnyPatchesBundlePath]; + BunnyLog(@"Bundle exists at path: %d", bundleExists); + + NSError *error = nil; + NSArray *bundleContents = [[NSFileManager defaultManager] + contentsOfDirectoryAtPath:bunnyPatchesBundlePath + error:&error]; + if (error) { + BunnyLog(@"Error listing bundle contents: %@", error); + } else { + BunnyLog(@"Bundle contents: %@", bundleContents); } -} \ No newline at end of file + + pyoncordDirectory = getPyoncordDirectory(); + loaderConfig = [[LoaderConfig alloc] init]; + [loaderConfig loadConfig]; + + %init; + } +} diff --git a/Sources/Utils.m b/Sources/Utils.m index e370a54..911a46f 100644 --- a/Sources/Utils.m +++ b/Sources/Utils.m @@ -1,69 +1,75 @@ #import "Utils.h" NSURL *getPyoncordDirectory(void) { - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSURL *documentDirectoryURL = [[fileManager URLsForDirectory:NSDocumentDirectory - inDomains:NSUserDomainMask] lastObject]; - - NSURL *pyoncordFolderURL = [documentDirectoryURL URLByAppendingPathComponent:@"pyoncord"]; - - if (![fileManager fileExistsAtPath:pyoncordFolderURL.path]) { - [fileManager createDirectoryAtURL:pyoncordFolderURL - withIntermediateDirectories:YES - attributes:nil - error:nil]; - } - - return pyoncordFolderURL; + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSURL *documentDirectoryURL = + [[fileManager URLsForDirectory:NSDocumentDirectory + inDomains:NSUserDomainMask] lastObject]; + + NSURL *pyoncordFolderURL = + [documentDirectoryURL URLByAppendingPathComponent:@"pyoncord"]; + + if (![fileManager fileExistsAtPath:pyoncordFolderURL.path]) { + [fileManager createDirectoryAtURL:pyoncordFolderURL + withIntermediateDirectories:YES + attributes:nil + error:nil]; + } + + return pyoncordFolderURL; } UIColor *hexToUIColor(NSString *hex) { - if (![hex hasPrefix:@"#"]) { - return nil; - } - - NSString *hexColor = [hex substringFromIndex:1]; - if (hexColor.length == 6) { - hexColor = [hexColor stringByAppendingString:@"ff"]; - } - - if (hexColor.length == 8) { - unsigned int hexNumber; - NSScanner *scanner = [NSScanner scannerWithString:hexColor]; - if ([scanner scanHexInt:&hexNumber]) { - CGFloat r = ((hexNumber & 0xFF000000) >> 24) / 255.0; - CGFloat g = ((hexNumber & 0x00FF0000) >> 16) / 255.0; - CGFloat b = ((hexNumber & 0x0000FF00) >> 8) / 255.0; - CGFloat a = (hexNumber & 0x000000FF) / 255.0; - - return [UIColor colorWithRed:r green:g blue:b alpha:a]; - } - } - + if (![hex hasPrefix:@"#"]) { return nil; + } + + NSString *hexColor = [hex substringFromIndex:1]; + if (hexColor.length == 6) { + hexColor = [hexColor stringByAppendingString:@"ff"]; + } + + if (hexColor.length == 8) { + unsigned int hexNumber; + NSScanner *scanner = [NSScanner scannerWithString:hexColor]; + if ([scanner scanHexInt:&hexNumber]) { + CGFloat r = ((hexNumber & 0xFF000000) >> 24) / 255.0; + CGFloat g = ((hexNumber & 0x00FF0000) >> 16) / 255.0; + CGFloat b = ((hexNumber & 0x0000FF00) >> 8) / 255.0; + CGFloat a = (hexNumber & 0x000000FF) / 255.0; + + return [UIColor colorWithRed:r green:g blue:b alpha:a]; + } + } + + return nil; } void showErrorAlert(NSString *title, NSString *message) { - dispatch_async(dispatch_get_main_queue(), ^{ - UIAlertController *alert = [UIAlertController alertControllerWithTitle:title - message:message - preferredStyle:UIAlertControllerStyleAlert]; - - UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" - style:UIAlertActionStyleDefault - handler:nil]; - - [alert addAction:okAction]; - - UIWindow *window = nil; - NSArray *windows = [[UIApplication sharedApplication] windows]; - for (UIWindow *w in windows) { - if (w.isKeyWindow) { - window = w; - break; - } - } - - [window.rootViewController presentViewController:alert animated:YES completion:nil]; - }); -} \ No newline at end of file + dispatch_async(dispatch_get_main_queue(), ^{ + UIAlertController *alert = [UIAlertController + alertControllerWithTitle:title + message:message + preferredStyle:UIAlertControllerStyleAlert]; + + UIAlertAction *okAction = + [UIAlertAction actionWithTitle:@"OK" + style:UIAlertActionStyleDefault + handler:nil]; + + [alert addAction:okAction]; + + UIWindow *window = nil; + NSArray *windows = [[UIApplication sharedApplication] windows]; + for (UIWindow *w in windows) { + if (w.isKeyWindow) { + window = w; + break; + } + } + + [window.rootViewController presentViewController:alert + animated:YES + completion:nil]; + }); +} \ No newline at end of file From c46c843b439b4dd616075fa0e61695056af588e0 Mon Sep 17 00:00:00 2001 From: Adrian Castro <22133246+castdrian@users.noreply.github.com> Date: Sat, 16 Nov 2024 01:04:15 +0100 Subject: [PATCH 07/10] fix: overwrite target bundle id --- .github/workflows/deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 11f71d5..fb060b0 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -138,6 +138,7 @@ jobs: -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="" \ From 49da1946c75e4f7f1e1c13283ba0409b569a9d09 Mon Sep 17 00:00:00 2001 From: Adrian Castro <22133246+castdrian@users.noreply.github.com> Date: Sat, 16 Nov 2024 01:32:54 +0100 Subject: [PATCH 08/10] chore: format workflow --- .github/workflows/deploy.yml | 40 +++++++++++++++++------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index fb060b0..24dd3dc 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -2,22 +2,21 @@ name: Deploy run-name: ${{ github.event.inputs.release == 'true' && 'Release' || 'Build' }} for ${{ github.event.inputs.ipa_url }} on: - workflow_dispatch: + workflow_dispatch: inputs: - ipa_url: - description: "Direct link to the decrypted IPA" - default: "" - required: true - type: string - release: - description: 'Release the build' - required: true - type: boolean + ipa_url: + description: "Direct link to the decrypted IPA" + default: "" + required: true + type: string + release: + description: "Release the build" + required: true + type: boolean env: GH_TOKEN: ${{ github.token }} - jobs: build-tweak: runs-on: macos-15 @@ -45,7 +44,7 @@ jobs: release_version=$(echo "$release_info" | jq -r '.assets[] | select(.name | contains("iphoneos-arm.deb")) | .name' | grep -o '_[0-9.]\+_' | tr -d '_') control_version=$(grep '^Version:' control | cut -d ' ' -f 2) - + if [ "$release_version" = "$control_version" ]; then echo "Versions match. Downloading DEB files..." echo "$release_info" | jq -r '.assets[] | select(.name | endswith(".deb")) | .browser_download_url' | xargs -I {} curl -L -O {} @@ -55,7 +54,7 @@ jobs: echo "DEB_DOWNLOADED=false" >> $GITHUB_ENV exit 0 fi - + - name: Check cache if: env.DEB_DOWNLOADED == 'false' run: echo upstream_heads=`git ls-remote https://github.com/theos/theos | head -n 1 | cut -f 1`-`git ls-remote https://github.com/theos/sdks | head -n 1 | cut -f 1` >> $GITHUB_ENV @@ -109,7 +108,7 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - + - name: Download build artifacts uses: actions/download-artifact@v4 with: @@ -157,13 +156,13 @@ jobs: ROOTLESS_DEB_FILE_NAME="${PACKAGE}_${VERSION}_iphoneos-arm64.deb" echo "ROOTLESS_DEB_FILE_NAME=$ROOTLESS_DEB_FILE_NAME" >> $GITHUB_ENV echo "APP_NAME=$NAME" >> $GITHUB_ENV - + - name: Run patcher run: | curl -L -o patcher https://github.com/amsyarasyiq/bunny-ipa-patcher/releases/download/release-pyon/patcher.mac-amd64 chmod +x patcher ./patcher -d discord.ipa -o discord.ipa -i ./ipa-icons.zip - + - name: Install cyan run: pip install --force-reinstall https://github.com/asdfzxcvbn/pyzule-rw/archive/main.zip Pillow @@ -174,8 +173,8 @@ jobs: - name: Upload ipa as artifact uses: actions/upload-artifact@v4 with: - name: ipa - path: ${{ env.APP_NAME }}.ipa + name: ipa + path: ${{ env.APP_NAME }}.ipa release-app: if: ${{ github.event.inputs.release == 'true' }} @@ -187,7 +186,7 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - + - name: Extract Values run: | NAME=$(grep '^Name:' control | cut -d ' ' -f 2) @@ -251,10 +250,9 @@ jobs: DOWNLOAD_URL=https://github.com/${{ github.repository }}/releases/download/v$VERSION/${{ env.APP_NAME }}.ipa NEW_ENTRY=$(jq -n --arg version "$VERSION" --arg date "$DATE" --arg size "$IPA_SIZE" --arg downloadURL "$DOWNLOAD_URL" '{version: $version, date: $date, size: ($size | tonumber), downloadURL: $downloadURL}') jq --argjson newEntry "$NEW_ENTRY" '.apps[0].versions |= [$newEntry] + .' app-repo.json > temp.json && mv temp.json app-repo.json - + - uses: EndBug/add-and-commit@v9 with: default_author: github_actions message: "chore: update app-repo.json" add: app-repo.json - \ No newline at end of file From e95baf59ca13a7ce7397295625c355850085c05f Mon Sep 17 00:00:00 2001 From: Adrian Castro <22133246+castdrian@users.noreply.github.com> Date: Sat, 16 Nov 2024 12:52:17 +0100 Subject: [PATCH 09/10] chore: add ci workflow --- .github/workflows/ci.yml | 13 +++++++++++++ .github/workflows/deploy.yml | 16 +++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2a90e51 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,13 @@ +name: Build Validation + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + build-check: + name: Build Check + uses: ./.github/workflows/deploy.yml + with: + caller_workflow: ci + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 24dd3dc..fa118b7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,4 +1,4 @@ -name: Deploy +name: Deploy Build run-name: ${{ github.event.inputs.release == 'true' && 'Release' || 'Build' }} for ${{ github.event.inputs.ipa_url }} on: @@ -13,6 +13,11 @@ on: description: "Release the build" required: true type: boolean + workflow_call: + inputs: + caller_workflow: + required: false + type: string env: GH_TOKEN: ${{ github.token }} @@ -21,6 +26,9 @@ jobs: build-tweak: runs-on: macos-15 + env: + DEB_DOWNLOADED: false + steps: - name: Checkout code uses: actions/checkout@v4 @@ -28,6 +36,7 @@ jobs: submodules: true - name: Download Tweak + if: inputs.caller_workflow != 'ci' run: | set +e @@ -102,6 +111,7 @@ jobs: path: ${{ env.ROOTLESS_DEB_PATH }} build-ipa: + if: inputs.caller_workflow != 'ci' runs-on: macos-15 needs: build-tweak @@ -177,7 +187,7 @@ jobs: path: ${{ env.APP_NAME }}.ipa release-app: - if: ${{ github.event.inputs.release == 'true' }} + if: inputs.caller_workflow != 'ci' && github.event.inputs.release == 'true' runs-on: macos-15 needs: build-ipa permissions: @@ -224,7 +234,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} app-repo: - if: ${{ github.event.inputs.release == 'true' }} + if: inputs.caller_workflow != 'ci' && github.event.inputs.release == 'true' continue-on-error: true runs-on: macos-15 needs: release-app From 072bfffb16ec48fb04dcc76e890286d8c16aa08d Mon Sep 17 00:00:00 2001 From: Adrian Castro <22133246+castdrian@users.noreply.github.com> Date: Sat, 16 Nov 2024 13:32:47 +0100 Subject: [PATCH 10/10] chore: add clang-format rules --- .clang-format | 14 ++ Headers/Fonts.h | 3 +- Headers/RCTCxxBridge.h | 4 +- Headers/Theme.h | 3 +- Sources/Fonts.x | 290 ++++++++++++++++----------------- Sources/LoaderConfig.m | 148 ++++++++--------- Sources/Sideloading.x | 119 +++++++------- Sources/Theme.m | 124 +++++++-------- Sources/Tweak.x | 354 +++++++++++++++++++---------------------- Sources/Utils.m | 101 ++++++------ 10 files changed, 556 insertions(+), 604 deletions(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..eda6b7e --- /dev/null +++ b/.clang-format @@ -0,0 +1,14 @@ +BasedOnStyle: LLVM +IndentWidth: 4 +ObjCBlockIndentWidth: 4 +UseTab: Never +ColumnLimit: 100 +AccessModifierOffset: -4 +AllowShortBlocksOnASingleLine: Empty +AllowShortFunctionsOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Empty +AllowShortLoopsOnASingleLine: false +AlignConsecutiveAssignments: Consecutive \ No newline at end of file diff --git a/Headers/Fonts.h b/Headers/Fonts.h index d2f1f78..bbded1f 100644 --- a/Headers/Fonts.h +++ b/Headers/Fonts.h @@ -2,5 +2,4 @@ #import extern NSMutableDictionary *fontMap; -void patchFonts(NSDictionary *mainFonts, - NSString *fontDefName); \ No newline at end of file +void patchFonts(NSDictionary *mainFonts, NSString *fontDefName); \ No newline at end of file diff --git a/Headers/RCTCxxBridge.h b/Headers/RCTCxxBridge.h index 51134c3..14458e6 100644 --- a/Headers/RCTCxxBridge.h +++ b/Headers/RCTCxxBridge.h @@ -1,5 +1,3 @@ @interface RCTCxxBridge : NSObject -- (void)executeApplicationScript:(NSData *)script - url:(NSURL *)url - async:(BOOL)async; +- (void)executeApplicationScript:(NSData *)script url:(NSURL *)url async:(BOOL)async; @end \ No newline at end of file diff --git a/Headers/Theme.h b/Headers/Theme.h index da92361..e1c1d1d 100644 --- a/Headers/Theme.h +++ b/Headers/Theme.h @@ -1,6 +1,5 @@ #import #import -void swizzleDCDThemeColor( - NSDictionary *> *semanticColors); +void swizzleDCDThemeColor(NSDictionary *> *semanticColors); void swizzleUIColor(NSDictionary *rawColors); \ No newline at end of file diff --git a/Sources/Fonts.x b/Sources/Fonts.x index 3ca1fef..3fd06b0 100644 --- a/Sources/Fonts.x +++ b/Sources/Fonts.x @@ -10,178 +10,168 @@ NSMutableDictionary *fontMap; %hook UIFont + (UIFont *)fontWithName:(NSString *)name size:(CGFloat)size { - NSString *replacementName = fontMap[name]; - if (replacementName) { - UIFontDescriptor *replacementDescriptor = - [UIFontDescriptor fontDescriptorWithName:replacementName size:size]; - UIFontDescriptor *fallbackDescriptor = - [replacementDescriptor fontDescriptorByAddingAttributes:@{ - UIFontDescriptorNameAttribute : @[ name ] - }]; - UIFontDescriptor *finalDescriptor = - [replacementDescriptor fontDescriptorByAddingAttributes:@{ - UIFontDescriptorCascadeListAttribute : @[ fallbackDescriptor ] - }]; - - return [UIFont fontWithDescriptor:finalDescriptor size:size]; - } - return %orig; + NSString *replacementName = fontMap[name]; + if (replacementName) { + UIFontDescriptor *replacementDescriptor = + [UIFontDescriptor fontDescriptorWithName:replacementName size:size]; + UIFontDescriptor *fallbackDescriptor = + [replacementDescriptor fontDescriptorByAddingAttributes:@{ + UIFontDescriptorNameAttribute : @[ name ] + }]; + UIFontDescriptor *finalDescriptor = + [replacementDescriptor fontDescriptorByAddingAttributes:@{ + UIFontDescriptorCascadeListAttribute : @[ fallbackDescriptor ] + }]; + + return [UIFont fontWithDescriptor:finalDescriptor size:size]; + } + return %orig; } -+ (UIFont *)fontWithDescriptor:(UIFontDescriptor *)descriptor - size:(CGFloat)size { - NSString *replacementName = fontMap[descriptor.postscriptName]; - if (replacementName) { - UIFontDescriptor *replacementDescriptor = - [UIFontDescriptor fontDescriptorWithName:replacementName size:size]; - UIFontDescriptor *finalDescriptor = - [replacementDescriptor fontDescriptorByAddingAttributes:@{ - UIFontDescriptorCascadeListAttribute : @[ descriptor ] - }]; - - return [UIFont fontWithDescriptor:finalDescriptor size:size]; - } - return %orig; ++ (UIFont *)fontWithDescriptor:(UIFontDescriptor *)descriptor size:(CGFloat)size { + NSString *replacementName = fontMap[descriptor.postscriptName]; + if (replacementName) { + UIFontDescriptor *replacementDescriptor = + [UIFontDescriptor fontDescriptorWithName:replacementName size:size]; + UIFontDescriptor *finalDescriptor = + [replacementDescriptor fontDescriptorByAddingAttributes:@{ + UIFontDescriptorCascadeListAttribute : @[ descriptor ] + }]; + + return [UIFont fontWithDescriptor:finalDescriptor size:size]; + } + return %orig; } + (UIFont *)systemFontOfSize:(CGFloat)size { - NSString *replacementName = fontMap[@"systemFont"]; - if (replacementName) { - return [UIFont fontWithName:replacementName size:size]; - } - return %orig; + NSString *replacementName = fontMap[@"systemFont"]; + if (replacementName) { + return [UIFont fontWithName:replacementName size:size]; + } + return %orig; } + (UIFont *)preferredFontForTextStyle:(UIFontTextStyle)style { - NSString *replacementName = fontMap[@"systemFont"]; - if (replacementName) { - return [UIFont fontWithName:replacementName size:[UIFont systemFontSize]]; - } - return %orig; + NSString *replacementName = fontMap[@"systemFont"]; + if (replacementName) { + return [UIFont fontWithName:replacementName size:[UIFont systemFontSize]]; + } + return %orig; } %end -void patchFonts(NSDictionary *mainFonts, - NSString *fontDefName) { - BunnyLog(@"patchFonts called with fonts: %@ and def name: %@", mainFonts, - fontDefName); - - if (!fontMap) { - BunnyLog(@"Creating new fontMap"); - fontMap = [NSMutableDictionary dictionary]; - } - - NSString *fontJson = [NSString - stringWithContentsOfURL:[getPyoncordDirectory() - URLByAppendingPathComponent:@"fonts.json"] - encoding:NSUTF8StringEncoding - error:nil]; - if (fontJson) { - BunnyLog(@"Found existing fonts.json: %@", fontJson); - } - - for (NSString *fontName in mainFonts) { - NSString *url = mainFonts[fontName]; - BunnyLog(@"Replacing font %@ with URL: %@", fontName, url); - - NSURL *fontURL = [NSURL URLWithString:url]; - NSString *fontExtension = fontURL.pathExtension; - - NSURL *fontCachePath = [[[getPyoncordDirectory() - URLByAppendingPathComponent:@"downloads" - isDirectory:YES] URLByAppendingPathComponent:@"fonts" - isDirectory:YES] - URLByAppendingPathComponent:fontDefName - isDirectory:YES]; - - fontCachePath = [fontCachePath - URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", - fontName, - fontExtension]]; - - NSURL *parentDir = [fontCachePath URLByDeletingLastPathComponent]; - if (![[NSFileManager defaultManager] fileExistsAtPath:parentDir.path]) { - BunnyLog(@"Creating parent directory: %@", parentDir.path); - [[NSFileManager defaultManager] createDirectoryAtURL:parentDir - withIntermediateDirectories:YES - attributes:nil - error:nil]; +void patchFonts(NSDictionary *mainFonts, NSString *fontDefName) { + BunnyLog(@"patchFonts called with fonts: %@ and def name: %@", mainFonts, fontDefName); + + if (!fontMap) { + BunnyLog(@"Creating new fontMap"); + fontMap = [NSMutableDictionary dictionary]; } - if (![[NSFileManager defaultManager] fileExistsAtPath:fontCachePath.path]) { - BunnyLog(@"Downloading font %@ from %@", fontName, url); - NSData *data = [NSData dataWithContentsOfURL:fontURL]; - if (data) { - BunnyLog(@"Writing font data to: %@", fontCachePath.path); - [data writeToURL:fontCachePath atomically:YES]; - } + NSString *fontJson = [NSString + stringWithContentsOfURL:[getPyoncordDirectory() URLByAppendingPathComponent:@"fonts.json"] + encoding:NSUTF8StringEncoding + error:nil]; + if (fontJson) { + BunnyLog(@"Found existing fonts.json: %@", fontJson); } - NSData *fontData = [NSData dataWithContentsOfURL:fontCachePath]; - if (fontData) { - BunnyLog(@"Registering font %@ with provider", fontName); - CGDataProviderRef provider = - CGDataProviderCreateWithCFData((__bridge CFDataRef)fontData); - CGFontRef font = CGFontCreateWithDataProvider(provider); - - if (font) { - CFStringRef postScriptName = CGFontCopyPostScriptName(font); - - CTFontRef existingFont = CTFontCreateWithName(postScriptName, 0, NULL); - if (existingFont) { - CFErrorRef unregisterError = NULL; - if (!CTFontManagerUnregisterGraphicsFont(font, &unregisterError)) { - BunnyLog(@"Failed to deregister font %@: %@", - (__bridge NSString *)postScriptName, - unregisterError - ? (__bridge NSString *)CFErrorCopyDescription( - unregisterError) - : @"Unknown error"); - if (unregisterError) - CFRelease(unregisterError); - } - CFRelease(existingFont); + for (NSString *fontName in mainFonts) { + NSString *url = mainFonts[fontName]; + BunnyLog(@"Replacing font %@ with URL: %@", fontName, url); + + NSURL *fontURL = [NSURL URLWithString:url]; + NSString *fontExtension = fontURL.pathExtension; + + NSURL *fontCachePath = [[[getPyoncordDirectory() URLByAppendingPathComponent:@"downloads" + isDirectory:YES] + URLByAppendingPathComponent:@"fonts" + isDirectory:YES] URLByAppendingPathComponent:fontDefName + isDirectory:YES]; + + fontCachePath = [fontCachePath + URLByAppendingPathComponent:[NSString + stringWithFormat:@"%@.%@", fontName, fontExtension]]; + + NSURL *parentDir = [fontCachePath URLByDeletingLastPathComponent]; + if (![[NSFileManager defaultManager] fileExistsAtPath:parentDir.path]) { + BunnyLog(@"Creating parent directory: %@", parentDir.path); + [[NSFileManager defaultManager] createDirectoryAtURL:parentDir + withIntermediateDirectories:YES + attributes:nil + error:nil]; } - CFErrorRef error = NULL; - if (CTFontManagerRegisterGraphicsFont(font, &error)) { - fontMap[fontName] = (__bridge NSString *)postScriptName; - BunnyLog(@"Successfully registered font %@ to %@", fontName, - (__bridge NSString *)postScriptName); - - NSError *jsonError; - NSData *jsonData = - [NSJSONSerialization dataWithJSONObject:fontMap - options:0 - error:&jsonError]; - if (!jsonError) { - [jsonData - writeToURL:[getPyoncordDirectory() - URLByAppendingPathComponent:@"fontMap.json"] - atomically:YES]; - } - } else { - NSString *errorDesc = - error ? (__bridge NSString *)CFErrorCopyDescription(error) - : @"Unknown error"; - BunnyLog(@"Failed to register font %@: %@", fontName, errorDesc); - if (error) - CFRelease(error); + if (![[NSFileManager defaultManager] fileExistsAtPath:fontCachePath.path]) { + BunnyLog(@"Downloading font %@ from %@", fontName, url); + NSData *data = [NSData dataWithContentsOfURL:fontURL]; + if (data) { + BunnyLog(@"Writing font data to: %@", fontCachePath.path); + [data writeToURL:fontCachePath atomically:YES]; + } } - CFRelease(postScriptName); - CFRelease(font); - } - CGDataProviderRelease(provider); + NSData *fontData = [NSData dataWithContentsOfURL:fontCachePath]; + if (fontData) { + BunnyLog(@"Registering font %@ with provider", fontName); + CGDataProviderRef provider = + CGDataProviderCreateWithCFData((__bridge CFDataRef)fontData); + CGFontRef font = CGFontCreateWithDataProvider(provider); + + if (font) { + CFStringRef postScriptName = CGFontCopyPostScriptName(font); + + CTFontRef existingFont = CTFontCreateWithName(postScriptName, 0, NULL); + if (existingFont) { + CFErrorRef unregisterError = NULL; + if (!CTFontManagerUnregisterGraphicsFont(font, &unregisterError)) { + BunnyLog(@"Failed to deregister font %@: %@", + (__bridge NSString *)postScriptName, + unregisterError + ? (__bridge NSString *)CFErrorCopyDescription(unregisterError) + : @"Unknown error"); + if (unregisterError) + CFRelease(unregisterError); + } + CFRelease(existingFont); + } + + CFErrorRef error = NULL; + if (CTFontManagerRegisterGraphicsFont(font, &error)) { + fontMap[fontName] = (__bridge NSString *)postScriptName; + BunnyLog(@"Successfully registered font %@ to %@", fontName, + (__bridge NSString *)postScriptName); + + NSError *jsonError; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:fontMap + options:0 + error:&jsonError]; + if (!jsonError) { + [jsonData writeToURL:[getPyoncordDirectory() + URLByAppendingPathComponent:@"fontMap.json"] + atomically:YES]; + } + } else { + NSString *errorDesc = error ? (__bridge NSString *)CFErrorCopyDescription(error) + : @"Unknown error"; + BunnyLog(@"Failed to register font %@: %@", fontName, errorDesc); + if (error) + CFRelease(error); + } + + CFRelease(postScriptName); + CFRelease(font); + } + CGDataProviderRelease(provider); + } } - } } %ctor { - @autoreleasepool { - fontMap = [NSMutableDictionary dictionary]; - BunnyLog(@"Font hooks initialized"); - %init; - } + @autoreleasepool { + fontMap = [NSMutableDictionary dictionary]; + BunnyLog(@"Font hooks initialized"); + %init; + } } diff --git a/Sources/LoaderConfig.m b/Sources/LoaderConfig.m index 693764d..3a8612e 100644 --- a/Sources/LoaderConfig.m +++ b/Sources/LoaderConfig.m @@ -5,107 +5,93 @@ @implementation LoaderConfig - (instancetype)init { - self = [super init]; - if (self) { - self.customLoadUrlEnabled = NO; - self.customLoadUrl = - [NSURL URLWithString:@"http://localhost:4040/bunny.js"]; - } - return self; + self = [super init]; + if (self) { + self.customLoadUrlEnabled = NO; + self.customLoadUrl = [NSURL URLWithString:@"http://localhost:4040/bunny.js"]; + } + return self; } - (BOOL)loadConfig { - NSURL *loaderConfigUrl = - [getPyoncordDirectory() URLByAppendingPathComponent:@"loader.json"]; - BunnyLog(@"Attempting to load config from: %@", loaderConfigUrl.path); - - if ([[NSFileManager defaultManager] fileExistsAtPath:loaderConfigUrl.path]) { - NSError *error = nil; - NSData *data = [NSData dataWithContentsOfURL:loaderConfigUrl]; - NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data - options:0 - error:&error]; - - if (error) { - BunnyLog(@"Error parsing loader config: %@", error); - return NO; - } + NSURL *loaderConfigUrl = [getPyoncordDirectory() URLByAppendingPathComponent:@"loader.json"]; + BunnyLog(@"Attempting to load config from: %@", loaderConfigUrl.path); + + if ([[NSFileManager defaultManager] fileExistsAtPath:loaderConfigUrl.path]) { + NSError *error = nil; + NSData *data = [NSData dataWithContentsOfURL:loaderConfigUrl]; + NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; - if (json) { - NSDictionary *customLoadUrl = json[@"customLoadUrl"]; - if (customLoadUrl) { - self.customLoadUrlEnabled = [customLoadUrl[@"enabled"] boolValue]; - NSString *urlString = customLoadUrl[@"url"]; - if (urlString) { - self.customLoadUrl = [NSURL URLWithString:urlString]; + if (error) { + BunnyLog(@"Error parsing loader config: %@", error); + return NO; } - } - BunnyLog(@"Loader config loaded - Custom URL %@: %@", - self.customLoadUrlEnabled ? @"enabled" : @"disabled", - self.customLoadUrl.absoluteString); - return YES; + if (json) { + NSDictionary *customLoadUrl = json[@"customLoadUrl"]; + if (customLoadUrl) { + self.customLoadUrlEnabled = [customLoadUrl[@"enabled"] boolValue]; + NSString *urlString = customLoadUrl[@"url"]; + if (urlString) { + self.customLoadUrl = [NSURL URLWithString:urlString]; + } + } + + BunnyLog(@"Loader config loaded - Custom URL %@: %@", + self.customLoadUrlEnabled ? @"enabled" : @"disabled", + self.customLoadUrl.absoluteString); + return YES; + } } - } - BunnyLog(@"Using default loader config: %@", - self.customLoadUrl.absoluteString); - return NO; + BunnyLog(@"Using default loader config: %@", self.customLoadUrl.absoluteString); + return NO; } + (instancetype)defaultConfig { - LoaderConfig *config = [[LoaderConfig alloc] init]; - config.customLoadUrlEnabled = NO; - config.customLoadUrl = - [NSURL URLWithString:@"http://localhost:4040/bunny.js"]; - return config; + LoaderConfig *config = [[LoaderConfig alloc] init]; + config.customLoadUrlEnabled = NO; + config.customLoadUrl = [NSURL URLWithString:@"http://localhost:4040/bunny.js"]; + return config; } + (instancetype)getLoaderConfig { - BunnyLog(@"Getting loader config"); - - NSURL *loaderConfigUrl = - [getPyoncordDirectory() URLByAppendingPathComponent:@"loader.json"]; - - if ([[NSFileManager defaultManager] fileExistsAtPath:loaderConfigUrl.path]) { - NSError *error = nil; - NSData *data = [NSData dataWithContentsOfURL:loaderConfigUrl]; - NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data - options:0 - error:&error]; - - if (json && !error) { - LoaderConfig *config = [[LoaderConfig alloc] init]; - NSDictionary *customLoadUrl = json[@"customLoadUrl"]; - if (customLoadUrl) { - config.customLoadUrlEnabled = [customLoadUrl[@"enabled"] boolValue]; - NSString *urlString = customLoadUrl[@"url"]; - if (urlString) { - config.customLoadUrl = [NSURL URLWithString:urlString]; + BunnyLog(@"Getting loader config"); + + NSURL *loaderConfigUrl = [getPyoncordDirectory() URLByAppendingPathComponent:@"loader.json"]; + + if ([[NSFileManager defaultManager] fileExistsAtPath:loaderConfigUrl.path]) { + NSError *error = nil; + NSData *data = [NSData dataWithContentsOfURL:loaderConfigUrl]; + NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; + + if (json && !error) { + LoaderConfig *config = [[LoaderConfig alloc] init]; + NSDictionary *customLoadUrl = json[@"customLoadUrl"]; + if (customLoadUrl) { + config.customLoadUrlEnabled = [customLoadUrl[@"enabled"] boolValue]; + NSString *urlString = customLoadUrl[@"url"]; + if (urlString) { + config.customLoadUrl = [NSURL URLWithString:urlString]; + } + } + return config; } - } - return config; } - } - BunnyLog(@"Couldn't get loader config"); - return [LoaderConfig defaultConfig]; + BunnyLog(@"Couldn't get loader config"); + return [LoaderConfig defaultConfig]; } - (BOOL)saveConfig { - NSURL *loaderConfigUrl = - [getPyoncordDirectory() URLByAppendingPathComponent:@"loader.json"]; - NSDictionary *json = @{ - @"customLoadUrl" : @{ - @"enabled" : @(self.customLoadUrlEnabled), - @"url" : self.customLoadUrl.absoluteString - } - }; - - NSData *data = [NSJSONSerialization dataWithJSONObject:json - options:0 - error:nil]; - return [data writeToURL:loaderConfigUrl atomically:YES]; + NSURL *loaderConfigUrl = [getPyoncordDirectory() URLByAppendingPathComponent:@"loader.json"]; + NSDictionary *json = @{ + @"customLoadUrl" : + @{@"enabled" : @(self.customLoadUrlEnabled), @"url" : self.customLoadUrl.absoluteString} + }; + + NSData *data = [NSJSONSerialization dataWithJSONObject:json options:0 error:nil]; + return [data writeToURL:loaderConfigUrl atomically:YES]; } @end \ No newline at end of file diff --git a/Sources/Sideloading.x b/Sources/Sideloading.x index a6fb206..8907d00 100644 --- a/Sources/Sideloading.x +++ b/Sources/Sideloading.x @@ -7,97 +7,92 @@ #define DISCORD_NAME @"Discord" static NSString *getAccessGroupID(void) { - NSDictionary *query = @{ - (__bridge NSString *) - kSecClass : (__bridge NSString *)kSecClassGenericPassword, - (__bridge NSString *)kSecAttrAccount : @"bundleSeedID", - (__bridge NSString *)kSecAttrService : @"", - (__bridge NSString *)kSecReturnAttributes : @YES - }; - - CFDictionaryRef result = NULL; - OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, - (CFTypeRef *)&result); - - if (status == errSecItemNotFound) { - status = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef *)&result); - } - - if (status != errSecSuccess) - return nil; - - NSString *accessGroup = [(__bridge NSDictionary *)result - objectForKey:(__bridge NSString *)kSecAttrAccessGroup]; - if (result) - CFRelease(result); - - return accessGroup; + NSDictionary *query = @{ + (__bridge NSString *)kSecClass : (__bridge NSString *)kSecClassGenericPassword, + (__bridge NSString *)kSecAttrAccount : @"bundleSeedID", + (__bridge NSString *)kSecAttrService : @"", + (__bridge NSString *)kSecReturnAttributes : @YES + }; + + CFDictionaryRef result = NULL; + OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result); + + if (status == errSecItemNotFound) { + status = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef *)&result); + } + + if (status != errSecSuccess) + return nil; + + NSString *accessGroup = + [(__bridge NSDictionary *)result objectForKey:(__bridge NSString *)kSecAttrAccessGroup]; + if (result) + CFRelease(result); + + return accessGroup; } static BOOL isSelfCall(void) { - NSArray *address = [NSThread callStackReturnAddresses]; - Dl_info info = {0}; - if (dladdr((void *)[address[2] longLongValue], &info) == 0) - return NO; - NSString *path = [NSString stringWithUTF8String:info.dli_fname]; - return [path hasPrefix:NSBundle.mainBundle.bundlePath]; + NSArray *address = [NSThread callStackReturnAddresses]; + Dl_info info = {0}; + if (dladdr((void *)[address[2] longLongValue], &info) == 0) + return NO; + NSString *path = [NSString stringWithUTF8String:info.dli_fname]; + return [path hasPrefix:NSBundle.mainBundle.bundlePath]; } %group Sideloading %hook NSBundle - (NSString *)bundleIdentifier { - return isSelfCall() ? DISCORD_BUNDLE_ID : %orig; + return isSelfCall() ? DISCORD_BUNDLE_ID : %orig; } - (NSDictionary *)infoDictionary { - if (!isSelfCall()) - return %orig; - - NSMutableDictionary *info = [%orig mutableCopy]; - info[@"CFBundleIdentifier"] = DISCORD_BUNDLE_ID; - info[@"CFBundleDisplayName"] = DISCORD_NAME; - info[@"CFBundleName"] = DISCORD_NAME; - return info; + if (!isSelfCall()) + return %orig; + + NSMutableDictionary *info = [%orig mutableCopy]; + info[@"CFBundleIdentifier"] = DISCORD_BUNDLE_ID; + info[@"CFBundleDisplayName"] = DISCORD_NAME; + info[@"CFBundleName"] = DISCORD_NAME; + return info; } - (id)objectForInfoDictionaryKey:(NSString *)key { - if (!isSelfCall()) - return %orig; + if (!isSelfCall()) + return %orig; - if ([key isEqualToString:@"CFBundleIdentifier"]) - return DISCORD_BUNDLE_ID; - if ([key isEqualToString:@"CFBundleDisplayName"] || - [key isEqualToString:@"CFBundleName"]) - return DISCORD_NAME; - return %orig; + if ([key isEqualToString:@"CFBundleIdentifier"]) + return DISCORD_BUNDLE_ID; + if ([key isEqualToString:@"CFBundleDisplayName"] || [key isEqualToString:@"CFBundleName"]) + return DISCORD_NAME; + return %orig; } %end %hook NSFileManager -- (NSURL *)containerURLForSecurityApplicationGroupIdentifier: - (NSString *)groupIdentifier { - BunnyLog(@"containerURLForSecurityApplicationGroupIdentifier called! %@", - groupIdentifier ?: @"nil"); - - NSArray *paths = [self URLsForDirectory:NSDocumentDirectory - inDomains:NSUserDomainMask]; - NSURL *lastPath = [paths lastObject]; - return [lastPath URLByAppendingPathComponent:@"AppGroup"]; +- (NSURL *)containerURLForSecurityApplicationGroupIdentifier:(NSString *)groupIdentifier { + BunnyLog(@"containerURLForSecurityApplicationGroupIdentifier called! %@", + groupIdentifier ?: @"nil"); + + NSArray *paths = [self URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask]; + NSURL *lastPath = [paths lastObject]; + return [lastPath URLByAppendingPathComponent:@"AppGroup"]; } %end %hook UIPasteboard - (NSString *)_accessGroup { - return getAccessGroupID(); + return getAccessGroupID(); } %end %end %ctor { - BOOL isAppStoreApp = [[NSFileManager defaultManager] - fileExistsAtPath:[[NSBundle mainBundle] appStoreReceiptURL].path]; - if (!isAppStoreApp) - %init(Sideloading); + BOOL isAppStoreApp = [[NSFileManager defaultManager] + fileExistsAtPath:[[NSBundle mainBundle] appStoreReceiptURL].path]; + if (!isAppStoreApp) + %init(Sideloading); } diff --git a/Sources/Theme.m b/Sources/Theme.m index ca4f792..e897918 100644 --- a/Sources/Theme.m +++ b/Sources/Theme.m @@ -3,82 +3,78 @@ #import "Utils.h" #import -void swizzleDCDThemeColor( - NSDictionary *> *semanticColors) { - BunnyLog(@"Swizzling DCDThemeColor"); - - Class DCDTheme = NSClassFromString(@"DCDTheme"); - Class dcdThemeTarget = object_getClass(DCDTheme); - - SEL themeIndexSelector = NSSelectorFromString(@"themeIndex"); - Method themeIndexMethod = - class_getClassMethod(dcdThemeTarget, themeIndexSelector); - IMP themeIndexImpl = method_getImplementation(themeIndexMethod); - int (*themeIndex)(id, SEL) = (int (*)(id, SEL))themeIndexImpl; - - Class DCDThemeColor = NSClassFromString(@"DCDThemeColor"); - Class target = object_getClass(DCDThemeColor); - - unsigned int methodCount; - Method *methods = class_copyMethodList(target, &methodCount); - - for (unsigned int i = 0; i < methodCount; i++) { - Method method = methods[i]; - SEL selector = method_getName(method); - NSString *methodName = NSStringFromSelector(selector); - - NSArray *semanticColor = semanticColors[methodName]; - if (semanticColor) { - BunnyLog(@"Swizzling %@", methodName); - - IMP originalImpl = method_getImplementation(method); - UIColor *(*original)(id, SEL) = (UIColor * (*)(id, SEL)) originalImpl; - - id block = ^UIColor *(id self) { - int themeIndexVal = themeIndex(dcdThemeTarget, themeIndexSelector); - if (semanticColor.count - 1 >= themeIndexVal) { - UIColor *semanticUIColor = hexToUIColor(semanticColor[themeIndexVal]); - if (semanticUIColor) { - return semanticUIColor; - } +void swizzleDCDThemeColor(NSDictionary *> *semanticColors) { + BunnyLog(@"Swizzling DCDThemeColor"); + + Class DCDTheme = NSClassFromString(@"DCDTheme"); + Class dcdThemeTarget = object_getClass(DCDTheme); + + SEL themeIndexSelector = NSSelectorFromString(@"themeIndex"); + Method themeIndexMethod = class_getClassMethod(dcdThemeTarget, themeIndexSelector); + IMP themeIndexImpl = method_getImplementation(themeIndexMethod); + int (*themeIndex)(id, SEL) = (int (*)(id, SEL))themeIndexImpl; + + Class DCDThemeColor = NSClassFromString(@"DCDThemeColor"); + Class target = object_getClass(DCDThemeColor); + + unsigned int methodCount; + Method *methods = class_copyMethodList(target, &methodCount); + + for (unsigned int i = 0; i < methodCount; i++) { + Method method = methods[i]; + SEL selector = method_getName(method); + NSString *methodName = NSStringFromSelector(selector); + + NSArray *semanticColor = semanticColors[methodName]; + if (semanticColor) { + BunnyLog(@"Swizzling %@", methodName); + + IMP originalImpl = method_getImplementation(method); + UIColor *(*original)(id, SEL) = (UIColor * (*)(id, SEL)) originalImpl; + + id block = ^UIColor *(id self) { + int themeIndexVal = themeIndex(dcdThemeTarget, themeIndexSelector); + if (semanticColor.count - 1 >= themeIndexVal) { + UIColor *semanticUIColor = hexToUIColor(semanticColor[themeIndexVal]); + if (semanticUIColor) { + return semanticUIColor; + } + } + return original(target, selector); + }; + + IMP newImpl = imp_implementationWithBlock(block); + method_setImplementation(method, newImpl); } - return original(target, selector); - }; - - IMP newImpl = imp_implementationWithBlock(block); - method_setImplementation(method, newImpl); } - } - free(methods); + free(methods); } void swizzleUIColor(NSDictionary *rawColors) { - BunnyLog(@"Swizzling UIColor"); + BunnyLog(@"Swizzling UIColor"); - Class UIColorClass = NSClassFromString(@"UIColor"); - Class target = object_getClass(UIColorClass); + Class UIColorClass = NSClassFromString(@"UIColor"); + Class target = object_getClass(UIColorClass); - unsigned int methodCount; - Method *methods = class_copyMethodList(target, &methodCount); + unsigned int methodCount; + Method *methods = class_copyMethodList(target, &methodCount); - for (unsigned int i = 0; i < methodCount; i++) { - Method method = methods[i]; - SEL selector = method_getName(method); - NSString *methodName = NSStringFromSelector(selector); + for (unsigned int i = 0; i < methodCount; i++) { + Method method = methods[i]; + SEL selector = method_getName(method); + NSString *methodName = NSStringFromSelector(selector); - NSString *rawColor = rawColors[methodName]; - if (rawColor) { - BunnyLog(@"Swizzling %@", methodName); + NSString *rawColor = rawColors[methodName]; + if (rawColor) { + BunnyLog(@"Swizzling %@", methodName); - id block = ^UIColor *(id self) { - return hexToUIColor(rawColor); - }; + id block = ^UIColor *(id self) { return hexToUIColor(rawColor); }; - IMP newImpl = imp_implementationWithBlock(block); - method_setImplementation(method, newImpl); + IMP newImpl = imp_implementationWithBlock(block); + method_setImplementation(method, newImpl); + } } - } - free(methods); + free(methods); } \ No newline at end of file diff --git a/Sources/Tweak.x b/Sources/Tweak.x index ed79a0a..e8a1287 100644 --- a/Sources/Tweak.x +++ b/Sources/Tweak.x @@ -14,203 +14,183 @@ static LoaderConfig *loaderConfig; %hook RCTCxxBridge -- (void)executeApplicationScript:(NSData *)script - url:(NSURL *)url - async:(BOOL)async { - if (![url.absoluteString containsString:@"main.jsbundle"]) { - return %orig; - } - - NSBundle *bunnyPatchesBundle = - [NSBundle bundleWithPath:bunnyPatchesBundlePath]; - if (!bunnyPatchesBundle) { - BunnyLog(@"Failed to load BunnyPatches bundle from path: %@", - bunnyPatchesBundlePath); - showErrorAlert( - @"Loader Error", - @"Failed to initialize mod loader. Please reinstall the tweak."); - return %orig; - } - - NSURL *patchPath = [bunnyPatchesBundle URLForResource:@"payload-base" - withExtension:@"js"]; - if (!patchPath) { - BunnyLog(@"Failed to find payload-base.js in bundle"); - showErrorAlert( - @"Loader Error", - @"Failed to initialize mod loader. Please reinstall the tweak."); - return %orig; - } - - NSData *patchData = [NSData dataWithContentsOfURL:patchPath]; - BunnyLog(@"Injecting loader"); - %orig(patchData, source, YES); - - __block NSData *bundle = [NSData - dataWithContentsOfURL:[pyoncordDirectory - URLByAppendingPathComponent:@"bundle.js"]]; - - dispatch_group_t group = dispatch_group_create(); - dispatch_group_enter(group); - - NSURL *bundleUrl; - if (loaderConfig.customLoadUrlEnabled && loaderConfig.customLoadUrl) { - bundleUrl = loaderConfig.customLoadUrl; - BunnyLog(@"Using custom load URL: %@", bundleUrl.absoluteString); - } else { - bundleUrl = [NSURL URLWithString:@"https://raw.githubusercontent.com/" - @"bunny-mod/builds/main/bunny.min.js"]; - BunnyLog(@"Using default bundle URL: %@", bundleUrl.absoluteString); - } - - NSMutableURLRequest *bundleRequest = [NSMutableURLRequest - requestWithURL:bundleUrl - cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData - timeoutInterval:3.0]; - - NSString *bundleEtag = [NSString - stringWithContentsOfURL:[pyoncordDirectory - URLByAppendingPathComponent:@"etag.txt"] - encoding:NSUTF8StringEncoding - error:nil]; - if (bundleEtag && bundle) { - [bundleRequest setValue:bundleEtag forHTTPHeaderField:@"If-None-Match"]; - } - - NSURLSession *session = - [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration - defaultSessionConfiguration]]; - [[session - dataTaskWithRequest:bundleRequest - completionHandler:^(NSData *data, NSURLResponse *response, - NSError *error) { - if ([response isKindOfClass:[NSHTTPURLResponse class]]) { - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; - if (httpResponse.statusCode == 200) { - bundle = data; - [bundle writeToURL:[pyoncordDirectory - URLByAppendingPathComponent:@"bundle.js"] - atomically:YES]; - - NSString *etag = - [httpResponse.allHeaderFields objectForKey:@"Etag"]; - if (etag) { - [etag writeToURL:[pyoncordDirectory - URLByAppendingPathComponent:@"etag.txt"] - atomically:YES - encoding:NSUTF8StringEncoding - error:nil]; +- (void)executeApplicationScript:(NSData *)script url:(NSURL *)url async:(BOOL)async { + if (![url.absoluteString containsString:@"main.jsbundle"]) { + return %orig; + } + + NSBundle *bunnyPatchesBundle = [NSBundle bundleWithPath:bunnyPatchesBundlePath]; + if (!bunnyPatchesBundle) { + BunnyLog(@"Failed to load BunnyPatches bundle from path: %@", bunnyPatchesBundlePath); + showErrorAlert(@"Loader Error", + @"Failed to initialize mod loader. Please reinstall the tweak."); + return %orig; + } + + NSURL *patchPath = [bunnyPatchesBundle URLForResource:@"payload-base" withExtension:@"js"]; + if (!patchPath) { + BunnyLog(@"Failed to find payload-base.js in bundle"); + showErrorAlert(@"Loader Error", + @"Failed to initialize mod loader. Please reinstall the tweak."); + return %orig; + } + + NSData *patchData = [NSData dataWithContentsOfURL:patchPath]; + BunnyLog(@"Injecting loader"); + %orig(patchData, source, YES); + + __block NSData *bundle = + [NSData dataWithContentsOfURL:[pyoncordDirectory URLByAppendingPathComponent:@"bundle.js"]]; + + dispatch_group_t group = dispatch_group_create(); + dispatch_group_enter(group); + + NSURL *bundleUrl; + if (loaderConfig.customLoadUrlEnabled && loaderConfig.customLoadUrl) { + bundleUrl = loaderConfig.customLoadUrl; + BunnyLog(@"Using custom load URL: %@", bundleUrl.absoluteString); + } else { + bundleUrl = [NSURL URLWithString:@"https://raw.githubusercontent.com/" + @"bunny-mod/builds/main/bunny.min.js"]; + BunnyLog(@"Using default bundle URL: %@", bundleUrl.absoluteString); + } + + NSMutableURLRequest *bundleRequest = + [NSMutableURLRequest requestWithURL:bundleUrl + cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData + timeoutInterval:3.0]; + + NSString *bundleEtag = [NSString + stringWithContentsOfURL:[pyoncordDirectory URLByAppendingPathComponent:@"etag.txt"] + encoding:NSUTF8StringEncoding + error:nil]; + if (bundleEtag && bundle) { + [bundleRequest setValue:bundleEtag forHTTPHeaderField:@"If-None-Match"]; + } + + NSURLSession *session = [NSURLSession + sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; + [[session + dataTaskWithRequest:bundleRequest + completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + if ([response isKindOfClass:[NSHTTPURLResponse class]]) { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + if (httpResponse.statusCode == 200) { + bundle = data; + [bundle + writeToURL:[pyoncordDirectory URLByAppendingPathComponent:@"bundle.js"] + atomically:YES]; + + NSString *etag = [httpResponse.allHeaderFields objectForKey:@"Etag"]; + if (etag) { + [etag + writeToURL:[pyoncordDirectory URLByAppendingPathComponent:@"etag.txt"] + atomically:YES + encoding:NSUTF8StringEncoding + error:nil]; + } + } } - } - } - dispatch_group_leave(group); - }] resume]; - - dispatch_group_wait(group, DISPATCH_TIME_FOREVER); - - NSString *themeString = [NSString - stringWithContentsOfURL: - [pyoncordDirectory URLByAppendingPathComponent:@"current-theme.json"] - encoding:NSUTF8StringEncoding - error:nil]; - if (themeString) { - NSString *jsCode = - [NSString stringWithFormat:@"globalThis.__PYON_LOADER__.storedTheme=%@", - themeString]; - %orig([jsCode dataUsingEncoding:NSUTF8StringEncoding], source, - async); - } - - NSData *fontData = [NSData - dataWithContentsOfURL:[pyoncordDirectory - URLByAppendingPathComponent:@"fonts.json"]]; - if (fontData) { - NSError *jsonError; - NSDictionary *fontDict = - [NSJSONSerialization JSONObjectWithData:fontData - options:0 - error:&jsonError]; - if (!jsonError && fontDict[@"main"]) { - BunnyLog(@"Found font configuration, applying..."); - patchFonts(fontDict[@"main"], fontDict[@"name"]); + dispatch_group_leave(group); + }] resume]; + + dispatch_group_wait(group, DISPATCH_TIME_FOREVER); + + NSString *themeString = + [NSString stringWithContentsOfURL:[pyoncordDirectory + URLByAppendingPathComponent:@"current-theme.json"] + encoding:NSUTF8StringEncoding + error:nil]; + if (themeString) { + NSString *jsCode = + [NSString stringWithFormat:@"globalThis.__PYON_LOADER__.storedTheme=%@", themeString]; + %orig([jsCode dataUsingEncoding:NSUTF8StringEncoding], source, async); } - } - - if (bundle) { - BunnyLog(@"Executing JS bundle"); - %orig(bundle, source, async); - } - - NSURL *preloadsDirectory = - [pyoncordDirectory URLByAppendingPathComponent:@"preloads"]; - if ([[NSFileManager defaultManager] - fileExistsAtPath:preloadsDirectory.path]) { - NSError *error = nil; - NSArray *contents = [[NSFileManager defaultManager] - contentsOfDirectoryAtURL:preloadsDirectory - includingPropertiesForKeys:nil - options:0 - error:&error]; - if (!error) { - for (NSURL *fileURL in contents) { - if ([[fileURL pathExtension] isEqualToString:@"js"]) { - BunnyLog(@"Executing preload JS file %@", fileURL.absoluteString); - NSData *data = [NSData dataWithContentsOfURL:fileURL]; - if (data) { - %orig(data, source, async); - } + + NSData *fontData = [NSData + dataWithContentsOfURL:[pyoncordDirectory URLByAppendingPathComponent:@"fonts.json"]]; + if (fontData) { + NSError *jsonError; + NSDictionary *fontDict = [NSJSONSerialization JSONObjectWithData:fontData + options:0 + error:&jsonError]; + if (!jsonError && fontDict[@"main"]) { + BunnyLog(@"Found font configuration, applying..."); + patchFonts(fontDict[@"main"], fontDict[@"name"]); } - } - } else { - BunnyLog(@"Error reading contents of preloads directory"); } - } - %orig(script, url, async); + if (bundle) { + BunnyLog(@"Executing JS bundle"); + %orig(bundle, source, async); + } + + NSURL *preloadsDirectory = [pyoncordDirectory URLByAppendingPathComponent:@"preloads"]; + if ([[NSFileManager defaultManager] fileExistsAtPath:preloadsDirectory.path]) { + NSError *error = nil; + NSArray *contents = + [[NSFileManager defaultManager] contentsOfDirectoryAtURL:preloadsDirectory + includingPropertiesForKeys:nil + options:0 + error:&error]; + if (!error) { + for (NSURL *fileURL in contents) { + if ([[fileURL pathExtension] isEqualToString:@"js"]) { + BunnyLog(@"Executing preload JS file %@", fileURL.absoluteString); + NSData *data = [NSData dataWithContentsOfURL:fileURL]; + if (data) { + %orig(data, source, async); + } + } + } + } else { + BunnyLog(@"Error reading contents of preloads directory"); + } + } + + %orig(script, url, async); } %end %ctor { - @autoreleasepool { - source = [NSURL URLWithString:@"bunny"]; - - NSString *install_prefix = @"/var/jb"; - isJailbroken = - [[NSFileManager defaultManager] fileExistsAtPath:install_prefix]; - - NSString *bundlePath = - [NSString stringWithFormat: - @"%@/Library/Application Support/BunnyResources.bundle", - install_prefix]; - BunnyLog(@"Is jailbroken: %d", isJailbroken); - BunnyLog(@"Bundle path for jailbroken: %@", bundlePath); - - NSString *jailedPath = [[NSBundle mainBundle].bundleURL.path - stringByAppendingPathComponent:@"BunnyResources.bundle"]; - BunnyLog(@"Bundle path for jailed: %@", jailedPath); - - bunnyPatchesBundlePath = isJailbroken ? bundlePath : jailedPath; - BunnyLog(@"Selected bundle path: %@", bunnyPatchesBundlePath); - - BOOL bundleExists = [[NSFileManager defaultManager] - fileExistsAtPath:bunnyPatchesBundlePath]; - BunnyLog(@"Bundle exists at path: %d", bundleExists); - - NSError *error = nil; - NSArray *bundleContents = [[NSFileManager defaultManager] - contentsOfDirectoryAtPath:bunnyPatchesBundlePath - error:&error]; - if (error) { - BunnyLog(@"Error listing bundle contents: %@", error); - } else { - BunnyLog(@"Bundle contents: %@", bundleContents); - } + @autoreleasepool { + source = [NSURL URLWithString:@"bunny"]; + + NSString *install_prefix = @"/var/jb"; + isJailbroken = [[NSFileManager defaultManager] fileExistsAtPath:install_prefix]; + + NSString *bundlePath = + [NSString stringWithFormat:@"%@/Library/Application Support/BunnyResources.bundle", + install_prefix]; + BunnyLog(@"Is jailbroken: %d", isJailbroken); + BunnyLog(@"Bundle path for jailbroken: %@", bundlePath); + + NSString *jailedPath = [[NSBundle mainBundle].bundleURL.path + stringByAppendingPathComponent:@"BunnyResources.bundle"]; + BunnyLog(@"Bundle path for jailed: %@", jailedPath); + + bunnyPatchesBundlePath = isJailbroken ? bundlePath : jailedPath; + BunnyLog(@"Selected bundle path: %@", bunnyPatchesBundlePath); + + BOOL bundleExists = + [[NSFileManager defaultManager] fileExistsAtPath:bunnyPatchesBundlePath]; + BunnyLog(@"Bundle exists at path: %d", bundleExists); + + NSError *error = nil; + NSArray *bundleContents = + [[NSFileManager defaultManager] contentsOfDirectoryAtPath:bunnyPatchesBundlePath + error:&error]; + if (error) { + BunnyLog(@"Error listing bundle contents: %@", error); + } else { + BunnyLog(@"Bundle contents: %@", bundleContents); + } - pyoncordDirectory = getPyoncordDirectory(); - loaderConfig = [[LoaderConfig alloc] init]; - [loaderConfig loadConfig]; + pyoncordDirectory = getPyoncordDirectory(); + loaderConfig = [[LoaderConfig alloc] init]; + [loaderConfig loadConfig]; - %init; - } + %init; + } } diff --git a/Sources/Utils.m b/Sources/Utils.m index 911a46f..bf4d274 100644 --- a/Sources/Utils.m +++ b/Sources/Utils.m @@ -1,75 +1,70 @@ #import "Utils.h" NSURL *getPyoncordDirectory(void) { - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSURL *documentDirectoryURL = - [[fileManager URLsForDirectory:NSDocumentDirectory - inDomains:NSUserDomainMask] lastObject]; + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSURL *documentDirectoryURL = [[fileManager URLsForDirectory:NSDocumentDirectory + inDomains:NSUserDomainMask] lastObject]; - NSURL *pyoncordFolderURL = - [documentDirectoryURL URLByAppendingPathComponent:@"pyoncord"]; + NSURL *pyoncordFolderURL = [documentDirectoryURL URLByAppendingPathComponent:@"pyoncord"]; - if (![fileManager fileExistsAtPath:pyoncordFolderURL.path]) { - [fileManager createDirectoryAtURL:pyoncordFolderURL - withIntermediateDirectories:YES - attributes:nil - error:nil]; - } + if (![fileManager fileExistsAtPath:pyoncordFolderURL.path]) { + [fileManager createDirectoryAtURL:pyoncordFolderURL + withIntermediateDirectories:YES + attributes:nil + error:nil]; + } - return pyoncordFolderURL; + return pyoncordFolderURL; } UIColor *hexToUIColor(NSString *hex) { - if (![hex hasPrefix:@"#"]) { - return nil; - } + if (![hex hasPrefix:@"#"]) { + return nil; + } - NSString *hexColor = [hex substringFromIndex:1]; - if (hexColor.length == 6) { - hexColor = [hexColor stringByAppendingString:@"ff"]; - } + NSString *hexColor = [hex substringFromIndex:1]; + if (hexColor.length == 6) { + hexColor = [hexColor stringByAppendingString:@"ff"]; + } - if (hexColor.length == 8) { - unsigned int hexNumber; - NSScanner *scanner = [NSScanner scannerWithString:hexColor]; - if ([scanner scanHexInt:&hexNumber]) { - CGFloat r = ((hexNumber & 0xFF000000) >> 24) / 255.0; - CGFloat g = ((hexNumber & 0x00FF0000) >> 16) / 255.0; - CGFloat b = ((hexNumber & 0x0000FF00) >> 8) / 255.0; - CGFloat a = (hexNumber & 0x000000FF) / 255.0; + if (hexColor.length == 8) { + unsigned int hexNumber; + NSScanner *scanner = [NSScanner scannerWithString:hexColor]; + if ([scanner scanHexInt:&hexNumber]) { + CGFloat r = ((hexNumber & 0xFF000000) >> 24) / 255.0; + CGFloat g = ((hexNumber & 0x00FF0000) >> 16) / 255.0; + CGFloat b = ((hexNumber & 0x0000FF00) >> 8) / 255.0; + CGFloat a = (hexNumber & 0x000000FF) / 255.0; - return [UIColor colorWithRed:r green:g blue:b alpha:a]; + return [UIColor colorWithRed:r green:g blue:b alpha:a]; + } } - } - return nil; + return nil; } void showErrorAlert(NSString *title, NSString *message) { - dispatch_async(dispatch_get_main_queue(), ^{ - UIAlertController *alert = [UIAlertController - alertControllerWithTitle:title - message:message - preferredStyle:UIAlertControllerStyleAlert]; + dispatch_async(dispatch_get_main_queue(), ^{ + UIAlertController *alert = + [UIAlertController alertControllerWithTitle:title + message:message + preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *okAction = - [UIAlertAction actionWithTitle:@"OK" - style:UIAlertActionStyleDefault - handler:nil]; + UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" + style:UIAlertActionStyleDefault + handler:nil]; - [alert addAction:okAction]; + [alert addAction:okAction]; - UIWindow *window = nil; - NSArray *windows = [[UIApplication sharedApplication] windows]; - for (UIWindow *w in windows) { - if (w.isKeyWindow) { - window = w; - break; - } - } + UIWindow *window = nil; + NSArray *windows = [[UIApplication sharedApplication] windows]; + for (UIWindow *w in windows) { + if (w.isKeyWindow) { + window = w; + break; + } + } - [window.rootViewController presentViewController:alert - animated:YES - completion:nil]; - }); + [window.rootViewController presentViewController:alert animated:YES completion:nil]; + }); } \ No newline at end of file