diff --git a/.vscode/settings.json b/.vscode/settings.json index 22a409b..2cf9559 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,12 @@ "MD029": false, "MD033": false, "MD045": false, - } + }, + "VsCodeTaskButtons.tasks": [ + { + "label": "$(tools) Make and Copy", + "task": "Make and Copy Tweak", + "tooltip": "Make and copy Tweak" + } + ], } diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..4980d74 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,35 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "shell", + "label": "Make and Copy Tweak", + "command": "zsh", + "args": [ + "-c", + "rm -rf packages && make clean && make package FINALPACKAGE=1 THEOS_PACKAGE_SCHEME=rootless && cp ./packages/app.pyoncord_*arm64.deb ~/Library/Mobile\\ Documents/com~apple~CloudDocs/" + ], + "problemMatcher": { + "owner": "cpp", + "pattern": { + "regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + }, + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "panel": "shared", + "showReuseMessage": false, + "clear": true, + "close": true + } + } + ] +} \ No newline at end of file diff --git a/BunnyTweak.plist b/Bunny.plist similarity index 100% rename from BunnyTweak.plist rename to Bunny.plist diff --git a/Headers/Fonts.h b/Headers/Fonts.h new file mode 100644 index 0000000..d1d1228 --- /dev/null +++ b/Headers/Fonts.h @@ -0,0 +1,5 @@ +#import +#import + +extern NSMutableDictionary *fontMap; +void patchFonts(NSDictionary *mainFonts, NSString *fontDefName); \ No newline at end of file diff --git a/Headers/LoaderConfig.h b/Headers/LoaderConfig.h new file mode 100644 index 0000000..6c63fa8 --- /dev/null +++ b/Headers/LoaderConfig.h @@ -0,0 +1,8 @@ +#import + +@interface LoaderConfig : NSObject +@property (nonatomic, assign) BOOL customLoadUrlEnabled; +@property (nonatomic, strong) NSURL *customLoadUrl; ++ (instancetype)defaultConfig; ++ (instancetype)getLoaderConfig; +@end \ No newline at end of file diff --git a/Headers/Logger.h b/Headers/Logger.h new file mode 100644 index 0000000..fce7b0e --- /dev/null +++ b/Headers/Logger.h @@ -0,0 +1,4 @@ +#import + +#define LOG_PREFIX @"[Bunny]" +#define BunnyLog(fmt, ...) NSLog((LOG_PREFIX @" " fmt), ##__VA_ARGS__) \ No newline at end of file diff --git a/Sources/BunnyTweakC/include/RCTCxxBridge.h b/Headers/RCTCxxBridge.h similarity index 81% rename from Sources/BunnyTweakC/include/RCTCxxBridge.h rename to Headers/RCTCxxBridge.h index d5bfd27..47093cc 100644 --- a/Sources/BunnyTweakC/include/RCTCxxBridge.h +++ b/Headers/RCTCxxBridge.h @@ -1,7 +1,3 @@ -@import Foundation; - @interface RCTCxxBridge : NSObject - - (void)executeApplicationScript:(NSData *)script url:(NSURL *)url async:(BOOL)async; - -@end +@end \ No newline at end of file diff --git a/Headers/Theme.h b/Headers/Theme.h new file mode 100644 index 0000000..b3be04f --- /dev/null +++ b/Headers/Theme.h @@ -0,0 +1,5 @@ +#import +#import + +void swizzleDCDThemeColor(NSDictionary *> *semanticColors); +void swizzleUIColor(NSDictionary *rawColors); \ No newline at end of file diff --git a/Headers/Utils.h b/Headers/Utils.h new file mode 100644 index 0000000..0b1fb2f --- /dev/null +++ b/Headers/Utils.h @@ -0,0 +1,6 @@ +#import +#import + +NSURL *getPyoncordDirectory(void); +UIColor *hexToUIColor(NSString *hex); +void showErrorAlert(NSString *title, NSString *message); \ No newline at end of file diff --git a/Makefile b/Makefile index 0b88da2..4f79052 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,30 @@ -ifeq ($(THEOS_PACKAGE_SCHEME),rootless) - TARGET := iphone:clang:latest:15.0 -else - TARGET := iphone:clang:latest:12.2 -endif - +TARGET := iphone:clang:latest:14.0 +ARCHS = arm64 +INSTALL_TARGET_PROCESSES = Discord include $(THEOS)/makefiles/common.mk -TWEAK_NAME = BunnyTweak +TWEAK_NAME = Bunny +BUNDLE_NAME = BunnyResources -BunnyTweak_FILES = $(shell find Sources/BunnyTweak -name '*.swift') $(shell find Sources/BunnyTweakC -name '*.m' -o -name '*.c' -o -name '*.mm' -o -name '*.cpp') -BunnyTweak_SWIFTFLAGS = -ISources/BunnyTweakC/include -BunnyTweak_CFLAGS = -fobjc-arc -ISources/BunnyTweakC/include +Bunny_FILES = $(wildcard Sources/*.x Sources/*.m Sources/**/*.x Sources/**/*.m) +Bunny_CFLAGS = -fobjc-arc -DPACKAGE_VERSION='@"$(THEOS_PACKAGE_BASE_VERSION)"' -I$(THEOS_PROJECT_DIR)/Headers +Bunny_FRAMEWORKS = Foundation UIKit CoreGraphics CoreText CoreFoundation -BunnyTweak_BUNDLE_NAME = BunnyPatches -BunnyTweak_BUNDLE_RESOURCE_DIRS = Resources +BunnyResources_INSTALL_PATH = "/Library/Application\ Support/" +BunnyResources_RESOURCE_DIRS = Resources include $(THEOS_MAKE_PATH)/tweak.mk +include $(THEOS_MAKE_PATH)/bundle.mk + +before-all:: + $(ECHO_NOTHING)mkdir -p Resources$(ECHO_END) + $(ECHO_NOTHING)sed -e 's/@PACKAGE_VERSION@/$(THEOS_PACKAGE_BASE_VERSION)/g' \ + -e 's/@TWEAK_NAME@/$(TWEAK_NAME)/g' \ + Sources/payload-base.template.js > Resources/payload-base.js$(ECHO_END) + +after-stage:: + $(ECHO_NOTHING)find $(THEOS_STAGING_DIR) -name ".DS_Store" -delete$(ECHO_END) + +after-package:: + $(ECHO_NOTHING)rm -rf Resources$(ECHO_END) diff --git a/Package.swift b/Package.swift deleted file mode 100644 index 1e010ca..0000000 --- a/Package.swift +++ /dev/null @@ -1,88 +0,0 @@ -// swift-tools-version:5.2 - -import PackageDescription -import Foundation - -let projectDir = URL(fileURLWithPath: #filePath).deletingLastPathComponent() - -@dynamicMemberLookup struct TheosConfiguration { - private let dict: [String: String] - init(at path: String) { - let configURL = URL(fileURLWithPath: path, relativeTo: projectDir) - guard let infoString = try? String(contentsOf: configURL) else { - fatalError(""" - Could not find Theos SPM config. Have you run `make spm` yet? - """) - } - let pairs = infoString.split(separator: "\n").map { - $0.split( - separator: "=", maxSplits: 1, - omittingEmptySubsequences: false - ).map(String.init) - }.map { ($0[0], $0[1]) } - dict = Dictionary(uniqueKeysWithValues: pairs) - } - subscript( - key: String, - or defaultValue: @autoclosure () -> String? = nil - ) -> String { - if let value = dict[key] { - return value - } else if let def = defaultValue() { - return def - } else { - fatalError(""" - Could not get value of key '\(key)' from Theos SPM config. \ - Try running `make spm` again. - """) - } - } - subscript(dynamicMember key: String) -> String { self[key] } -} -let conf = TheosConfiguration(at: ".theos/spm_config") - -let theosPath = conf.theos -let sdk = conf.sdk -let resourceDir = conf.swiftResourceDir -let deploymentTarget = conf.deploymentTarget -let triple = "arm64-apple-ios\(deploymentTarget)" - -let libFlags: [String] = [ - "-F\(theosPath)/vendor/lib", "-F\(theosPath)/lib", - "-I\(theosPath)/vendor/include", "-I\(theosPath)/include" -] - -let cFlags: [String] = libFlags + [ - "-target", triple, "-isysroot", sdk, - "-Wno-unused-command-line-argument", "-Qunused-arguments", -] - -let cxxFlags: [String] = [ -] - -let swiftFlags: [String] = libFlags + [ - "-target", triple, "-sdk", sdk, "-resource-dir", resourceDir, -] - -let package = Package( - name: "BunnyTweak", - platforms: [.iOS(deploymentTarget)], - products: [ - .library( - name: "BunnyTweak", - targets: ["BunnyTweak"] - ), - ], - targets: [ - .target( - name: "BunnyTweakC", - cSettings: [.unsafeFlags(cFlags)], - cxxSettings: [.unsafeFlags(cxxFlags)] - ), - .target( - name: "BunnyTweak", - dependencies: ["BunnyTweakC"], - swiftSettings: [.unsafeFlags(swiftFlags)] - ), - ] -) diff --git a/README.md b/README.md index 5203363..5b87c66 100644 --- a/README.md +++ b/README.md @@ -3,22 +3,22 @@ Tweak to inject [Bunny](https://github.com/pyoncord/Bunny) into Discord. Forked [VendettaTweak](https://github.com/vendetta-mod/VendettaTweak), modified to match with [BunnyXposed](https://github.com/pyoncord/BunnyXposed) behavior. There are still slight differences between these two, and this tweak may be missing some loader features. > [!NOTE] -> As of right now this tweak does not encompass some functionalities when running in a jailed environment with a wildcard certificate \ +> As of right now this tweak does not encompass some functionalities when running in a jailed environment with a distribution certificate that has a different App ID \ > If you value these features sign the application with a local dev certificate: -> - setAlternateAppIcon does not work, thus breaking dynamic app icons -> - sharing files to the application/selecting items via the Files app does not work +> +> - setAlternateAppIcon does not work, thus breaking dynamic app icons (unlikely to be able to be fixed unless iOS behavior changes) +> - sharing files to the application/selecting items via the Files app does not work (this might be more of a keychain/app group issue) ## Installation Builds can be found in the [Releases](https://github.com/pyoncord/BunnyTweak/releases/latest) tab. > [!NOTE] -> Raw decrypted IPAs which are used to provide prepatched IPAs are sourced from the [Enmity](https://github.com/enmity-mod/) community. These raw decrypted IPAs are also used throughout Enmity related projects such as [enmity-mod/tweak](https://github.com/enmity-mod/tweak/) and [acquitelol/rosiecord](https://github.com/acquitelol/rosiecord).\ -> All credits are attributed to the owner(s) of the raw IPAs. +> Decrypted IPAs are sourced from the [Enmity](https://github.com/enmity-mod/) community. These are also used throughout Enmity related projects such as [enmity-mod/tweak](https://github.com/enmity-mod/tweak/) and [acquitelol/rosiecord](https://github.com/acquitelol/rosiecord).\ +> All credits are attributed to the owner(s). ### Jailbroken -1. Install the Orion runtime via your preferred package manager, by adding `https://repo.chariz.com/` to your sources, then finding `Orion Runtime`. 1. Install Bunny by downloading the appropriate Debian package (or by building your own, see [Building BunnyTweak locally](#building-bunnytweak-locally)) and adding it to your package manager. Use the file ending in `arm.deb` for rootful jailbreaks, and the file ending in `arm64.deb` for rootless jailbreaks. ### Jailed @@ -34,7 +34,7 @@ Builds can be found in the [Releases](https://github.com/pyoncord/BunnyTweak/rel ## Building BunnyTweak locally > [!NOTE] -> These steps assume you use MacOS. +> These steps assume you use macOS. 1. Install Xcode from the App Store. If you've previously installed the `Command Line Utilities` package, you will need to run `sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer` to make sure you're using the Xcode tools instead. diff --git a/Sources/BunnyTweak/Fonts.x.swift b/Sources/BunnyTweak/Fonts.x.swift deleted file mode 100644 index acb6b16..0000000 --- a/Sources/BunnyTweak/Fonts.x.swift +++ /dev/null @@ -1,99 +0,0 @@ -import BunnyTweakC -import Orion -import UIKit -import os -import Foundation - -// This is not the full implementation, we only take what we need in here -// For full definition, find it from the core codebase (JS side) -struct FontDefinition: Codable { - let name: String - let main: [String: String]? -} - -var fontMap: [String: String] = [:] - -class FontsHook: ClassHook { - class func fontWithName(_ name: String, size: CGFloat) -> UIFont { - if let replacementName = fontMap[name] { - let replacementDescriptor = UIFontDescriptor(name: replacementName, size: size) - let fallbackDescriptor = replacementDescriptor.addingAttributes([.name: [name]]) - let finalDescriptor = replacementDescriptor.addingAttributes([.cascadeList: [fallbackDescriptor]]) - - return orig.fontWithDescriptor(finalDescriptor, size: size) - } - - return orig.fontWithName(name, size: size) - } - class func fontWithDescriptor(_ descriptor: UIFontDescriptor, size: CGFloat) -> UIFont { - if let replacementName = fontMap[descriptor.postscriptName] { - let replacementDescriptor = UIFontDescriptor(name: replacementName, size: size) - let finalDescriptor = replacementDescriptor.addingAttributes([.cascadeList: [descriptor]]) - - return orig.fontWithDescriptor(finalDescriptor, size: size) - } - - return orig.fontWithDescriptor(descriptor, size: size) - } -} - -func patchFonts(_ main: [String: String], fontDefName: String) { - for (fontName, url) in main { - os_log("Replacing font %{public}@ with URL: %{public}@", log: bunnyLog, type: .info, fontName, url) - - let fontExtension = URL(string: url)!.pathExtension - let fontCachePath = pyoncordDirectory - .appendingPathComponent("downloads", isDirectory: true) - .appendingPathComponent("fonts", isDirectory: true) - .appendingPathComponent(fontDefName, isDirectory: true) - .appendingPathComponent("\(fontName).\(fontExtension)") - - do { - os_log("Attempting to register font %{public}@ from %{public}@", log: bunnyLog, type: .info, fontName, url) - - let parent = fontCachePath.deletingLastPathComponent() - if !FileManager.default.fileExists(atPath: parent.path) { - os_log("Creating parent directory: %{public}@", log: bunnyLog, type: .debug, parent.path) - try FileManager.default.createDirectory( - at: parent, withIntermediateDirectories: true, attributes: nil) - } - - // JS side should download these already, but just in case... - if !FileManager.default.fileExists(atPath: fontCachePath.path) { - os_log("Downloading font %{public}@ from %{public}@", log: bunnyLog, type: .debug, fontName, url) - if let data = try? Data(contentsOf: URL(string: url)!) { - os_log("Writing font data to: %{public}@", log: bunnyLog, type: .debug, fontCachePath.path) - try? data.write(to: fontCachePath) - } - } - - if let data = try? Data(contentsOf: fontCachePath) { - os_log("Registering font %{public}@ with provider", log: bunnyLog, type: .debug, fontName) - let provider = CGDataProvider(data: data as CFData) - let font = CGFont(provider!) - var error: Unmanaged? - - // This does not work with system/app fonts unfortunately. Throws a CTFontManagerError.systemRequired - if let existingFont = CGFont(font!.postScriptName!) { - var unregisterError: Unmanaged? - if !CTFontManagerUnregisterGraphicsFont(existingFont, &unregisterError) { - os_log("Failed to deregister font %{public}@: %{public}@", log: bunnyLog, type: .error, - font!.postScriptName! as String, - String(describing: unregisterError!.takeUnretainedValue())) - } - } - - if CTFontManagerRegisterGraphicsFont(font!, &error) { - fontMap[fontName] = font!.postScriptName! as String - os_log("Successfully registered font %{public}@ to %{public}@", log: bunnyLog, type: .info, fontName, font!.postScriptName! as String) - } else { - os_log("Failed to register font %{public}@: %{public}@", log: bunnyLog, type: .error, fontName, String(describing: error!.takeUnretainedValue())) - } - } else { - os_log("Failed to read font data from: %{public}@", log: bunnyLog, type: .error, fontCachePath.path) - } - } catch { - os_log("Failed to register font %{public}@: %{public}@", log: bunnyLog, type: .error, fontName, error.localizedDescription) - } - } -} \ No newline at end of file diff --git a/Sources/BunnyTweak/LoaderConfig.x.swift b/Sources/BunnyTweak/LoaderConfig.x.swift deleted file mode 100644 index 729e3ba..0000000 --- a/Sources/BunnyTweak/LoaderConfig.x.swift +++ /dev/null @@ -1,47 +0,0 @@ -import Foundation -import os - -enum LoaderConfigError: Error { - case doesNotExist -} - -struct CustomLoadUrl: Codable { - let enabled: Bool - let url: URL -} - -struct LoaderConfig: Codable { - let customLoadUrl: CustomLoadUrl -} - -let defaultLoaderConfig = LoaderConfig( - customLoadUrl: CustomLoadUrl( - enabled: false, - url: URL(string: "http://localhost:4040/bunny.js")! - ) -) - -let pyoncordDirectory = getPyoncordDirectory() -let loaderConfigUrl = pyoncordDirectory.appendingPathComponent("loader.json") - -func getLoaderConfig() -> LoaderConfig { - os_log("Getting loader config", log: bunnyLog, type: .debug) - let fileManager = FileManager.default - - do { - if fileManager.fileExists(atPath: loaderConfigUrl.path) { - let data = try Data(contentsOf: loaderConfigUrl) - let loaderConfig = try JSONDecoder().decode(LoaderConfig.self, from: data) - - os_log("Got loader config", log: bunnyLog, type: .debug) - - return loaderConfig - } else { - throw LoaderConfigError.doesNotExist - } - } catch { - os_log("Couldn't get loader config", log: bunnyLog, type: .error) - - return defaultLoaderConfig - } -} diff --git a/Sources/BunnyTweak/Themes.x.swift b/Sources/BunnyTweak/Themes.x.swift deleted file mode 100644 index 3eb8277..0000000 --- a/Sources/BunnyTweak/Themes.x.swift +++ /dev/null @@ -1,152 +0,0 @@ -import Orion -import UIKit -import BunnyTweakC -import os - -struct Author: Codable { - let name: String - let id: String? -} - -struct ThemeData: Codable { - let name: String - let description: String? - let authors: [Author]? - let spec: Int - let semanticColors: [String: [String]]? - let rawColors: [String: String]? -} - -struct Theme: Codable { - let id: String - let selected: Bool - let data: ThemeData -} - -func hexToUIColor(_ hex: String) -> UIColor? { - let r: CGFloat - let g: CGFloat - let b: CGFloat - let a: CGFloat - - if hex.hasPrefix("#") { - let start = hex.index(hex.startIndex, offsetBy: 1) - var hexColor = String(hex[start...]) - - if hexColor.count == 6 { - hexColor.append("ff") - } - - if hexColor.count == 8 { - let scanner = Scanner(string: hexColor) - var hexNumber: UInt64 = 0 - - if scanner.scanHexInt64(&hexNumber) { - r = CGFloat((hexNumber & 0xff00_0000) >> 24) / 255 - g = CGFloat((hexNumber & 0x00ff_0000) >> 16) / 255 - b = CGFloat((hexNumber & 0x0000_ff00) >> 8) / 255 - a = CGFloat(hexNumber & 0x0000_00ff) / 255 - - return UIColor(red: r, green: g, blue: b, alpha: a) - } - } - } - - return nil -} - -func swizzleDCDThemeColor(_ semanticColors: [String: [String]]) { - os_log("Swizzling DCDThemeColor", log: bunnyLog, type: .info) - - let DCDTheme: AnyClass = NSClassFromString("DCDTheme")! - let dcdThemeTarget: AnyClass = object_getClass(DCDTheme)! - - let themeIndexSelector = NSSelectorFromString("themeIndex") - let themeIndexMethod = class_getClassMethod(dcdThemeTarget, themeIndexSelector)! - let themeIndexImpl = method_getImplementation(themeIndexMethod) - typealias ThemeIndexType = @convention(c) (AnyObject, Selector) -> Int - let themeIndex: ThemeIndexType = unsafeBitCast(themeIndexImpl, to: ThemeIndexType.self) - - let DCDThemeColor: AnyClass = NSClassFromString("DCDThemeColor")! - os_log("Found instance of DCDThemeColor", log: bunnyLog, type: .debug) - let target: AnyClass = object_getClass(DCDThemeColor)! - - os_log("Found DCDThemeColor", log: bunnyLog, type: .debug) - - var methodCount: UInt32 = 0 - let methods = class_copyMethodList(target, &methodCount)! - - os_log("DCDThemeColor has %{public}d methods", log: bunnyLog, type: .debug, methodCount) - - for i in 0.. UIColor - let original = unsafeBitCast(originalImpl, to: OriginalType.self) - let semanticColorBlock: @convention(block) (AnyObject) -> UIColor = { - (self: AnyObject) -> UIColor in - let themeIndexVal = themeIndex(dcdThemeTarget, themeIndexSelector) - if semanticColor.count - 1 >= themeIndexVal { - if let semanticUIColor = hexToUIColor( - semanticColor[themeIndexVal]) - { - return semanticUIColor - } - } - - return original(target, selector) - } - let semanticColorImplementation = imp_implementationWithBlock( - unsafeBitCast(semanticColorBlock, to: AnyObject.self)) - method_setImplementation( - method, semanticColorImplementation) - - } - } - free(methods) -} - -func swizzleUIColor(_ rawColors: [String: String]) { - os_log("Swizzling UIColor", log: bunnyLog, type: .info) - - let UIColor: AnyClass = NSClassFromString("UIColor")! - os_log("Found instance of UIColor", log: bunnyLog, type: .debug) - let target: AnyClass = object_getClass(UIColor)! - - os_log("Found UIColor", log: bunnyLog, type: .debug) - - var methodCount: UInt32 = 0 - let methods = class_copyMethodList(target, &methodCount)! - - os_log("UIColor has %{public}d methods", log: bunnyLog, type: .debug, methodCount) - - for i in 0.. UIColor = { - (self: AnyObject) -> UIColor in - return hexToUIColor(rawColor)! - } - let rawColorImplementation = imp_implementationWithBlock( - unsafeBitCast(rawColorBlock, to: AnyObject.self)) - method_setImplementation( - method, rawColorImplementation) - - } - } - free(methods) -} diff --git a/Sources/BunnyTweak/Tweak.x.swift b/Sources/BunnyTweak/Tweak.x.swift deleted file mode 100644 index cde4f6b..0000000 --- a/Sources/BunnyTweak/Tweak.x.swift +++ /dev/null @@ -1,143 +0,0 @@ -import Orion -import BunnyTweakC -import os - -let bunnyLog = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "bunny") -let source = URL(string: "bunny")! - -let install_prefix = String(cString: get_install_prefix()) -let isJailbroken = FileManager.default.fileExists(atPath: "\(install_prefix)/Library/Application Support/BunnyTweak/BunnyPatches.bundle") - -let bunnyPatchesBundlePath = isJailbroken ? "\(install_prefix)/Library/Application Support/BunnyTweak/BunnyPatches.bundle" : "\(Bundle.main.bundleURL.path)/BunnyPatches.bundle" - -class FileManagerLoadHook: ClassHook { - func containerURLForSecurityApplicationGroupIdentifier(_ groupIdentifier: NSString?) -> URL? { - os_log("containerURLForSecurityApplicationGroupIdentifier called! %{public}@ groupIdentifier", log: bunnyLog, type: .debug, groupIdentifier ?? "nil") - - if (isJailbroken) { - return orig.containerURLForSecurityApplicationGroupIdentifier(groupIdentifier) - } - - let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) - let lastPath = paths.last! - return lastPath.appendingPathComponent("AppGroup") - } -} - -class LoadHook: ClassHook { - func executeApplicationScript(_ script: Data, url: URL, async: Bool) { - os_log("executeApplicationScript called!", log: bunnyLog, type: .debug) - - let loaderConfig = getLoaderConfig() - - let bunnyPatchesBundle = Bundle(path: bunnyPatchesBundlePath)! - - if let patchPath = bunnyPatchesBundle.url(forResource: "payload-base", withExtension: "js") { - let patchData = try! Data(contentsOf: patchPath) - os_log("Executing payload base", log: bunnyLog, type: .debug) - orig.executeApplicationScript(patchData, url: source, async: true) - } - - let pyoncordDirectory = getPyoncordDirectory() - - var bundle = try? Data(contentsOf: pyoncordDirectory.appendingPathComponent("bundle.js")) - - let group = DispatchGroup() - - group.enter() - var bundleUrl: URL - if loaderConfig.customLoadUrl.enabled { - os_log( - "Custom load URL enabled, with URL %{public}@ ", log: bunnyLog, type: .info, - loaderConfig.customLoadUrl.url.absoluteString) - bundleUrl = loaderConfig.customLoadUrl.url - } else { - bundleUrl = URL( - string: "https://raw.githubusercontent.com/pyoncord/bunny-builds/main/bunny.js")! - } - - os_log("Fetching JS bundle", log: bunnyLog, type: .info) - var bundleRequest = URLRequest( - url: bundleUrl, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 3.0) - - if let bundleEtag = try? String( - contentsOf: pyoncordDirectory.appendingPathComponent("etag.txt")), bundle != nil - { - bundleRequest.addValue(bundleEtag, forHTTPHeaderField: "If-None-Match") - } - - let fetchTask = URLSession.shared.dataTask(with: bundleRequest) { data, response, error in - if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 { - os_log("Successfully fetched JS Bundle", log: bunnyLog, type: .debug) - bundle = data - try? bundle?.write(to: pyoncordDirectory.appendingPathComponent("bundle.js")) - - let etag = httpResponse.allHeaderFields["Etag"] as? String - try? etag?.write( - to: pyoncordDirectory.appendingPathComponent("etag.txt"), atomically: true, - encoding: .utf8) - } - - group.leave() - } - - fetchTask.resume() - group.wait() - - if let themeString = try? String( - contentsOf: pyoncordDirectory.appendingPathComponent("current-theme.json")) - { - orig.executeApplicationScript( - "globalThis.__PYON_LOADER__.storedTheme=\(themeString)".data(using: .utf8)!, url: source, async: async) - } - - let preloadsDirectory = pyoncordDirectory.appendingPathComponent("preloads") - - if FileManager.default.fileExists(atPath: preloadsDirectory.path) { - do { - let contents = try FileManager.default.contentsOfDirectory( - at: preloadsDirectory, includingPropertiesForKeys: nil, options: []) - - for fileURL in contents { - if fileURL.pathExtension == "js" { - os_log( - "Executing preload JS file %{public}@ ", log: bunnyLog, type: .info, fileURL.absoluteString) - - if let data = try? Data(contentsOf: fileURL) { - orig.executeApplicationScript(data, url: source, async: async) - } - } - } - } catch { - os_log("Error reading contents of preloads directory", log: bunnyLog, type: .error) - } - } - - if bundle != nil { - os_log("Executing JS bundle", log: bunnyLog, type: .info) - orig.executeApplicationScript(bundle!, url: source, async: async) - } else { - os_log("Unable to fetch JS bundle", log: bunnyLog, type: .error) - } - - os_log("Executing original script", log: bunnyLog, type: .info) - orig.executeApplicationScript(script, url: url, async: async) - } -} - -struct BunnyTweak: Tweak { - func tweakDidActivate() { - if let themeData = try? Data( - contentsOf: pyoncordDirectory.appendingPathComponent("current-theme.json")) { - let theme = try? JSONDecoder().decode(Theme.self, from: themeData) - if let semanticColors = theme?.data.semanticColors { swizzleDCDThemeColor(semanticColors) } - if let rawColors = theme?.data.rawColors { swizzleUIColor(rawColors) } - } - - if let fontData = try? Data( - contentsOf: pyoncordDirectory.appendingPathComponent("fonts.json")) { - let fonts = try? JSONDecoder().decode(FontDefinition.self, from: fontData) - if let main = fonts?.main { patchFonts(main, fontDefName: fonts!.name) } - } - } -} diff --git a/Sources/BunnyTweak/Utils.x.swift b/Sources/BunnyTweak/Utils.x.swift deleted file mode 100644 index 48a0702..0000000 --- a/Sources/BunnyTweak/Utils.x.swift +++ /dev/null @@ -1,16 +0,0 @@ -import Foundation - -func getPyoncordDirectory() -> URL { - let documentDirectoryURL = try! FileManager.default.url( - for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) - - let pyoncordFolderURL = documentDirectoryURL.appendingPathComponent("pyoncord") - - // Create the "pyoncord" folder if it doesn't exist - if !FileManager.default.fileExists(atPath: pyoncordFolderURL.path) { - try! FileManager.default.createDirectory( - at: pyoncordFolderURL, withIntermediateDirectories: true, attributes: nil) - } - - return pyoncordFolderURL -} diff --git a/Sources/BunnyTweakC/Tweak.m b/Sources/BunnyTweakC/Tweak.m deleted file mode 100644 index 94f9277..0000000 --- a/Sources/BunnyTweakC/Tweak.m +++ /dev/null @@ -1,11 +0,0 @@ -#import - -const char * const _Nonnull get_install_prefix(void) { - return THEOS_PACKAGE_INSTALL_PREFIX; -} - -__attribute__((constructor)) static void init() { - // Initialize Orion - do not remove this line. - orion_init(); - // Custom initialization code goes here. -} diff --git a/Sources/BunnyTweakC/include/Tweak.h b/Sources/BunnyTweakC/include/Tweak.h deleted file mode 100644 index 0429e20..0000000 --- a/Sources/BunnyTweakC/include/Tweak.h +++ /dev/null @@ -1,3 +0,0 @@ -#import "RCTCxxBridge.h" - -const char * const _Nonnull get_install_prefix(void); diff --git a/Sources/BunnyTweakC/include/module.modulemap b/Sources/BunnyTweakC/include/module.modulemap deleted file mode 100644 index 2e175d3..0000000 --- a/Sources/BunnyTweakC/include/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module BunnyTweakC { - umbrella "." - export * -} diff --git a/Sources/Fonts.x b/Sources/Fonts.x new file mode 100644 index 0000000..e3ae4ee --- /dev/null +++ b/Sources/Fonts.x @@ -0,0 +1,150 @@ +#import "Fonts.h" +#import "Utils.h" +#import "Logger.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; +} + ++ (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; +} + ++ (UIFont *)preferredFontForTextStyle:(UIFontTextStyle)style { + 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]; + } + + 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); + } + + 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 diff --git a/Sources/LoaderConfig.m b/Sources/LoaderConfig.m new file mode 100644 index 0000000..bfc31f4 --- /dev/null +++ b/Sources/LoaderConfig.m @@ -0,0 +1,55 @@ +#import "LoaderConfig.h" +#import "Utils.h" +#import "Logger.h" + +@implementation LoaderConfig + ++ (instancetype)defaultConfig { + 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(@"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]; +} + +@end \ No newline at end of file diff --git a/Sources/Logger.m b/Sources/Logger.m new file mode 100644 index 0000000..9daa597 --- /dev/null +++ b/Sources/Logger.m @@ -0,0 +1 @@ +#import "Logger.h" diff --git a/Sources/Theme.m b/Sources/Theme.m new file mode 100644 index 0000000..a31c654 --- /dev/null +++ b/Sources/Theme.m @@ -0,0 +1,82 @@ +#import "Theme.h" +#import "Utils.h" +#import "Logger.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); + } + } + + 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); + } + } + + free(methods); +} \ No newline at end of file diff --git a/Sources/Tweak.x b/Sources/Tweak.x new file mode 100644 index 0000000..26f995f --- /dev/null +++ b/Sources/Tweak.x @@ -0,0 +1,187 @@ +#import +#import +#import "Utils.h" +#import "Logger.h" +#import "Theme.h" +#import "Fonts.h" +#import "LoaderConfig.h" + +static NSURL *source; +static BOOL isJailbroken; +static NSString *bunnyPatchesBundlePath; +static NSURL *pyoncordDirectory; +static LoaderConfig *loaderConfig; + +%hook NSFileManager + +- (NSURL *)containerURLForSecurityApplicationGroupIdentifier:(NSString *)groupIdentifier { + BunnyLog(@"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 { + 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"]); + } + } + + 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); + } + + pyoncordDirectory = getPyoncordDirectory(); + loaderConfig = [[LoaderConfig alloc] init]; + + %init; + } +} \ No newline at end of file diff --git a/Sources/Utils.m b/Sources/Utils.m new file mode 100644 index 0000000..e370a54 --- /dev/null +++ b/Sources/Utils.m @@ -0,0 +1,69 @@ +#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; +} + +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]; + } + } + + 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 diff --git a/Resources/payload-base.js b/Sources/payload-base.template.js similarity index 56% rename from Resources/payload-base.js rename to Sources/payload-base.template.js index a4b8743..7b0e1f2 100644 --- a/Resources/payload-base.js +++ b/Sources/payload-base.template.js @@ -1,7 +1,7 @@ globalThis.__PYON_LOADER__ = { - loaderName: "BunnyTweak", - loaderVersion: "0.3.2", + loaderName: "@TWEAK_NAME@", + loaderVersion: "@PACKAGE_VERSION@", hasThemeSupport: true, storedTheme: null, fontPatch: 2 -} +} \ No newline at end of file diff --git a/control b/control index a9f574d..7c3f94f 100644 --- a/control +++ b/control @@ -1,11 +1,11 @@ -Package: io.github.pyoncord.app +Package: app.pyoncord Name: Bunny -Version: 0.3.2 +Version: 0.4.0 Architecture: iphoneos-arm -Description: A mod for Discord's mobile apps. +Description: A client modification for Discord's mobile app. Maintainer: Pylix -Author: Pylix, FieryFlames +Author: Pylix, Adrian Castro, FieryFlames Icon: https://avatars.githubusercontent.com/u/132727175 Section: Tweaks -Depends: ${ORION}, firmware (>= 13.0) +Depends: mobilesubstrate (>= 0.9.5000) Conflicts: app.enmity, mod.vendetta