diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 3aae016..ec4c4a6 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -72,6 +72,10 @@ jobs: if: env.DEB_DOWNLOADED == 'false' uses: Randomblock1/theos-action@v1 + - name: Set GNU Make path + run: | + echo "PATH=$(brew --prefix make)/libexec/gnubin:$PATH" >> $GITHUB_ENV + - name: Build packages if: env.DEB_DOWNLOADED == 'false' run: make package FINALPACKAGE=1 && make package FINALPACKAGE=1 THEOS_PACKAGE_SCHEME=rootless diff --git a/Headers/LoaderConfig.h b/Headers/LoaderConfig.h index 6c63fa8..d15774d 100644 --- a/Headers/LoaderConfig.h +++ b/Headers/LoaderConfig.h @@ -5,4 +5,6 @@ @property (nonatomic, strong) NSURL *customLoadUrl; + (instancetype)defaultConfig; + (instancetype)getLoaderConfig; +- (instancetype)init; +- (BOOL)loadConfig; @end \ No newline at end of file diff --git a/Sources/LoaderConfig.m b/Sources/LoaderConfig.m index 11037ef..40ce65b 100644 --- a/Sources/LoaderConfig.m +++ b/Sources/LoaderConfig.m @@ -4,6 +4,92 @@ @implementation LoaderConfig +- (instancetype)init { + self = [super init]; + if (self) { + self.customLoadUrlEnabled = NO; + self.customLoadUrl = [NSURL URLWithString:@"http://localhost:4040/revenge.js"]; + } + return self; +} + +- (BOOL)loadConfig { + NSURL *loaderConfigUrl = [getPyoncordDirectory() URLByAppendingPathComponent:@"loader.json"]; + RevengeLog(@"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) { + RevengeLog(@"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]; + } + } + + RevengeLog(@"Loader config loaded - Custom URL %@: %@", + self.customLoadUrlEnabled ? @"enabled" : @"disabled", + self.customLoadUrl.absoluteString); + return YES; + } + } + + RevengeLog(@"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/revenge.js"]; + return config; +} + +- (BOOL)loadConfig { + NSURL *loaderConfigUrl = [getPyoncordDirectory() URLByAppendingPathComponent:@"loader.json"]; + RevengeLog(@"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) { + RevengeLog(@"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]; + } + } + + RevengeLog(@"Loader config loaded - Custom URL %@: %@", + self.customLoadUrlEnabled ? @"enabled" : @"disabled", + self.customLoadUrl.absoluteString); + return YES; + } + } + + RevengeLog(@"Using default loader config: %@", self.customLoadUrl.absoluteString); + return NO; +} + + (instancetype)defaultConfig { LoaderConfig *config = [[LoaderConfig alloc] init]; config.customLoadUrlEnabled = NO; diff --git a/Sources/Sideloading.x b/Sources/Sideloading.x new file mode 100644 index 0000000..9d08c24 --- /dev/null +++ b/Sources/Sideloading.x @@ -0,0 +1,88 @@ +#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; +} + +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]; +} + +%group Sideloading + +%hook NSBundle +- (NSString *)bundleIdentifier { + 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; +} + +- (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; + 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"]; +} +%end + +%hook UIPasteboard +- (NSString *)_accessGroup { + return getAccessGroupID(); +} +%end + +%end + +%ctor { + BOOL isAppStoreApp = [[NSFileManager defaultManager] fileExistsAtPath:[[NSBundle mainBundle] appStoreReceiptURL].path]; + if (!isAppStoreApp) %init(Sideloading); +} \ No newline at end of file diff --git a/Sources/Tweak.x b/Sources/Tweak.x index d543993..ca25390 100644 --- a/Sources/Tweak.x +++ b/Sources/Tweak.x @@ -12,23 +12,6 @@ static NSString *revengePatchesBundlePath; static NSURL *pyoncordDirectory; static LoaderConfig *loaderConfig; -%hook NSFileManager - -- (NSURL *)containerURLForSecurityApplicationGroupIdentifier:(NSString *)groupIdentifier { - RevengeLog(@"containerURLForSecurityApplicationGroupIdentifier called! %@", - groupIdentifier ?: @"nil"); - - if (isJailbroken) { - return %orig(groupIdentifier); - } - - NSArray *paths = [self URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask]; - NSURL *lastPath = [paths lastObject]; - return [lastPath URLByAppendingPathComponent:@"AppGroup"]; -} - -%end - %hook RCTCxxBridge - (void)executeApplicationScript:(NSData *)script url:(NSURL *)url async:(BOOL)async { @@ -179,9 +162,10 @@ static LoaderConfig *loaderConfig; RevengeLog(@"Bundle contents: %@", bundleContents); } - pyoncordDirectory = getPyoncordDirectory(); +pyoncordDirectory = getPyoncordDirectory(); loaderConfig = [[LoaderConfig alloc] init]; - + [loaderConfig loadConfig]; + %init; } } diff --git a/control b/control index d1a45b9..695e922 100644 --- a/control +++ b/control @@ -1,6 +1,6 @@ Package: io.github.revenge-mod.app Name: Revenge -Version: 2.0.0 +Version: 2.0.1 Architecture: iphoneos-arm Description: A mod for Discord's mobile apps. Maintainer: Cristiandis