From 60be3dd5d1362dadebc7560f1be10946be37e2e0 Mon Sep 17 00:00:00 2001 From: pylixonly <82711525+amsyarasyiq@users.noreply.github.com> Date: Sat, 18 May 2024 18:36:12 +0800 Subject: [PATCH] feat: support custom fonts (#6) + remove legacy LoaderConfig properties --- Resources/payload-base.js | 3 +- Sources/PyoncordTweak/Fonts.x.swift | 95 ++++++++++++++++++++++ Sources/PyoncordTweak/LoaderConfig.x.swift | 5 +- Sources/PyoncordTweak/Tweak.x.swift | 33 ++++---- 4 files changed, 115 insertions(+), 21 deletions(-) create mode 100644 Sources/PyoncordTweak/Fonts.x.swift diff --git a/Resources/payload-base.js b/Resources/payload-base.js index 188f8a5..8de0727 100644 --- a/Resources/payload-base.js +++ b/Resources/payload-base.js @@ -2,5 +2,6 @@ globalThis.__PYON_LOADER__ = { loaderName: "BunnyTweak", loaderVersion: "0.2.0", hasThemeSupport: true, - storedTheme: null + storedTheme: null, + fontPatch: 2 } diff --git a/Sources/PyoncordTweak/Fonts.x.swift b/Sources/PyoncordTweak/Fonts.x.swift new file mode 100644 index 0000000..468a73d --- /dev/null +++ b/Sources/PyoncordTweak/Fonts.x.swift @@ -0,0 +1,95 @@ +import PyoncordTweakC +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] { + return UIFont(name: replacementName, 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) + .addingAttributes(descriptor.fontAttributes) + + return orig.fontWithDescriptor(replacementDescriptor, 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: pyoncordLog, 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: pyoncordLog, type: .info, fontName, url) + + let parent = fontCachePath.deletingLastPathComponent() + if !FileManager.default.fileExists(atPath: parent.path) { + os_log("Creating parent directory: %{public}@", log: pyoncordLog, 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: pyoncordLog, type: .debug, fontName, url) + if let data = try? Data(contentsOf: URL(string: url)!) { + os_log("Writing font data to: %{public}@", log: pyoncordLog, type: .debug, fontCachePath.path) + try? data.write(to: fontCachePath) + } + } + + if let data = try? Data(contentsOf: fontCachePath) { + os_log("Registering font %{public}@ with provider", log: pyoncordLog, 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: pyoncordLog, 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: pyoncordLog, type: .info, fontName, font!.postScriptName! as String) + } else { + os_log("Failed to register font %{public}@: %{public}@", log: pyoncordLog, type: .error, fontName, String(describing: error!.takeUnretainedValue())) + } + } else { + os_log("Failed to read font data from: %{public}@", log: pyoncordLog, type: .error, fontCachePath.path) + } + } catch { + os_log("Failed to register font %{public}@: %{public}@", log: pyoncordLog, type: .error, fontName, error.localizedDescription) + } + } +} \ No newline at end of file diff --git a/Sources/PyoncordTweak/LoaderConfig.x.swift b/Sources/PyoncordTweak/LoaderConfig.x.swift index f450a76..96f2940 100644 --- a/Sources/PyoncordTweak/LoaderConfig.x.swift +++ b/Sources/PyoncordTweak/LoaderConfig.x.swift @@ -12,16 +12,13 @@ struct CustomLoadUrl: Codable { struct LoaderConfig: Codable { let customLoadUrl: CustomLoadUrl - let loadReactDevTools: Bool - var loadPyoncord: Bool? = nil } let defaultLoaderConfig = LoaderConfig( customLoadUrl: CustomLoadUrl( enabled: false, url: URL(string: "http://localhost:4040/bunny.js")! - ), - loadReactDevTools: false + ) ) let pyoncordDirectory = getPyoncordDirectory() diff --git a/Sources/PyoncordTweak/Tweak.x.swift b/Sources/PyoncordTweak/Tweak.x.swift index 7c536d0..c349bb8 100644 --- a/Sources/PyoncordTweak/Tweak.x.swift +++ b/Sources/PyoncordTweak/Tweak.x.swift @@ -40,7 +40,7 @@ class LoadHook: ClassHook { let pyoncordDirectory = getPyoncordDirectory() - var bundle = try? Data(contentsOf: pyoncordDirectory.appendingPathComponent("bunny.js")) + var bundle = try? Data(contentsOf: pyoncordDirectory.appendingPathComponent("bundle.js")) let group = DispatchGroup() @@ -52,13 +52,8 @@ class LoadHook: ClassHook { loaderConfig.customLoadUrl.url.absoluteString) bundleUrl = loaderConfig.customLoadUrl.url } else { - if loaderConfig.loadPyoncord == true { - bundleUrl = URL( - string: "https://raw.githubusercontent.com/pyoncord/pyoncord/builds/pyoncord.js")! - } else { - bundleUrl = URL( - string: "https://raw.githubusercontent.com/pyoncord/detta-builds/main/bunny.js")! - } + bundleUrl = URL( + string: "https://raw.githubusercontent.com/pyoncord/bunny-builds/main/bunny.js")! } os_log("Fetching JS bundle", log: pyoncordLog, type: .info) @@ -75,7 +70,7 @@ class LoadHook: ClassHook { if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 { os_log("Successfully fetched JS Bundle", log: pyoncordLog, type: .debug) bundle = data - try? bundle?.write(to: pyoncordDirectory.appendingPathComponent("bunny.js")) + try? bundle?.write(to: pyoncordDirectory.appendingPathComponent("bundle.js")) let etag = httpResponse.allHeaderFields["Etag"] as? String try? etag?.write( @@ -131,12 +126,18 @@ class LoadHook: ClassHook { } struct PyoncordTweak: 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) } - } + 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) } + } + } }