Cydia Substrate Pitfalls: Difference between revisions

From iPhone Development Wiki
(linking dyld_shared_cache to de-orphan it; adding a few other wikilinks)
m (Provide)
 
(16 intermediate revisions by 5 users not shown)
Line 1: Line 1:
Follow the following guidelines to avoid common MobileSubstrate mistakes:
Follow the following guidelines to avoid common [[MobileSubstrate]] mistakes. See also: [[Best Practices]].


* Do not use [[UIDevice]] in your constructor if you are hooking SpringBoard!
== Coding practices ==
 
* You generally should not use UIKit classes in a constructor function, as many of them rely on UIApplication already being initialized first. Using <code>[[UIDevice]]</code> too early can cause [https://www.idownloadblog.com/2011/10/08/how-to-fix-quarter-screen-issue-jailbroken-ios-5-install/ odd system behavior].
* Access ivars through their associated properties, where practical.
* Access ivars through their associated properties, where practical.
* Always call the old implementation of a method unless you require suppressing the default behaviour. Similarly, only call the old implementation of a method from within the method hook.
* Always call the old implementation of a method unless you require suppressing the default behavior. Similarly, only call the old implementation of a method from within that method’s hook.
* Always include a [[MobileSubstrate#MobileLoader|MobileLoader]] filter plist.
* Prefer to use documented/public SDK features when they exist; don’t use private APIs just because you can. Public APIs are guaranteed to always work, and won’t be removed in future releases.
** If you require access to only SpringBoard, use "com.apple.springboard"; if you require access to all UIKit apps, use "com.apple.UIKit".
* Avoid <code>[[UIAlertView]]</code> or <code>[[UIAlertController]]</code> in SpringBoard--it doesn't participate in the SpringBoard-wide alert system; use a custom <code>SBAlertItem</code> subclass instead (not necessary for cases where you don't care if the system destroys your alert.)
* Avoid UIAlertView in SpringBoard--it doesn't participate in the SpringBoard-wide alert system; use a custom SBAlertItem subclass instead (not necessary for cases where you don't care if the system destroys your alert.)
* Use Auto Layout to make your extension resolution-independent and rotation-aware. Using <code>-autoresizingMask</code> and <code>-layoutSubviews</code> may also be acceptable in some situations. However, be aware that <code>-layoutSubviews</code> can be invoked frequently, causing poor performance if your code is not well optimized.
* Use documented/public SDK features wherever practical. Apple usually tests private APIs internally before making them public; often the public version is the same or similar to the private version that was in previous OS releases.
* Preferences.framework changed drastically between 3.1 and 3.2. Use [[PreferenceLoader]] and standard preferences plists for simple panes, PSViewController + [[UITableView]] for more complex panes (PSListController is okay for simple preferences, but is less subject to OS changes; PSViewController is more powerful, but many implementations were broken between 3.1 and 3.2.)
* Use autoresizingMask and layoutSubviews to make your extension resolution-independent and rotation-aware.
* Don't modify OS or Cydia files; use runtime-replacement instead.
* Avoid doing extra work--the methods your extension hooks may be called from inside a tight loop; disk access is considered a LOT of extra work (related: properties are actually method calls, they can be slow.)
* Avoid doing extra work--the methods your extension hooks may be called from inside a tight loop; disk access is considered a LOT of extra work (related: properties are actually method calls, they can be slow.)
* UI* methods must be performed on the main thread (this includes [[UIImage]]! use CGImageSource and company if you need to load images from a background thread.)
* <code>UI*</code> methods must be performed on the main thread. This includes <code>[[UIImage]]</code>! Use <code>CGImageSource</code> and company if you need to load images on a background thread.
* Never block the main thread! Performing any operation that can take significant time on the main thread is evil (that includes '''any''' operation that hits the network.)
* Never block the main thread! Performing any operation that can take significant time on the main thread is evil (that includes '''any''' operation that hits the network.)
* Access resources through NSBundle so that they may be themed via WinterBoard.
* Access resources through <code>NSBundle</code> so that they may be themed via Anemone, SnowBoard, and WinterBoard.
* Include the proper Depends: line in your Debian control file (that way Cydia will automatically install dependencies and will alert users when they are missing required components like firmware version.)
* Don't make assumptions about the ivar layout of a class; these can and will change across firmware versions. Use <code>[[Hooking Instance Variables|MSHookIvar]]</code> or <code>-valueForKey:</code> instead.
* If your extension exposes an API, document it!
** Include headers '''inside''' your package so they're easy to find.
* Ensure your extension is localizable; use the standard .lproj system where possible. Avoid having images that include text.
* Don't make assumptions about the ivar layout of a class; these can and will change across firmware versions.
* Use an established hooking framework such as substrate.h, CaptainHook.h or [[Logos]]; there is little benefit in rolling your own, and too many details have to be just right.
* If accessing low-level graphics hardware, beware that first-generation devices do _not_ have a unified memory model (VRAM and regular RAM are separate regions.)
* If accessing low-level graphics hardware, beware that first-generation devices do _not_ have a unified memory model (VRAM and regular RAM are separate regions.)
* Participate in the memory warning system if your extension has a significant memory footprint; release everything you can.
* Participate in the memory warning system if your extension has a significant memory footprint; release everything you can.
* Use mmap when working with large datasets--the kernel will swap it in and out of memory as necessary (but avoid heavy writes, as that's no better than virtual memory.)
* Use <code>mmap()</code> when working with large datasets--the kernel will swap it in and out of memory as necessary (but avoid heavy writes, as that's no better than virtual memory.)
* Never inhibit MobileSubstrate's [[MobileSubstrate#Safe_mode|safe mode]].
* Decide how far back you want your package to support, and set the minimum deployment version to that. If you need to use newer APIs than your minimum version supports, use feature detection. In modern projects, this can be done using the special <code>if (@available(…))</code> macro, otherwise you can use [https://github.com/theos/headers/blob/master/version.h version.h] from Theos’s version.h.
* If your extension manipulates icon layouts, use IconSupport so you don't stomp all over other extensions' modifications or the stock/safe-mode layout.
* Strip symbols if you have something to hide; use standard C (or C++) for the hidden parts--Objective-C will spill your details.
* Compile against the earliest possible firmware you can (compile against 3.0, not 3.0.1, 3.1 or 3.1.2); use feature detection if necessary.
* Many private frameworks have public headers available for macOS; use them. Theos includes a few [https://github.com/theos/headers private headers] that fill some of the gaps.
* Only link to the frameworks you actually need--importing unnecessary frameworks will increase app launch time, and can trigger frequent daemon crashes on iOS 13+. Use NSBundle or dlopen/dlsym to load libraries and access symbols at runtime after the app has launched.
* Use a unique prefix on classes and category/<code>%new</code> methods: Objective-C has NO namespacing capabilities. (examples: two classes called Preferences that behave differently; two extensions define a <code>-[NSData base64String]</code> that behave slightly differently.) Examples: <code>ABCDMyClass</code> for a class, <code>abcd_myMethod</code> for a category method.
* Avoid waking the device or keeping the device from sleeping. If your extension needs to do something periodically, use [[PersistentConnection.framework|PersistentConnection]] to ensure the system wakes up the CPU and radios for you (if your timer needs to happen at an exact time), or executes it the next time the system wakes up (if your timer being delayed doesn’t matter).
* Avoid CPU-/GPU-/disk-intensive activity where the user would not expect it (battery life!)
* Don't overuse <code>NSLog(), CFLog(), fprintf(stderr, …);</code> they're slow and synchronous.
* If your extension includes a preference bundle, make sure to include an icon. These should be: icon.png at 29x29, [email protected] at 58x58, [email protected] at 87x87. This will be displayed at the root Settings app screen, so it is critical to have an icon, and match the Settings icon size convention.
* Avoid changing the class of objects returned by public APIs--some App Store applications perform unnecessary checks against the class of an object and will fail.
* Prefer binary plists over XML--they're much quicker (OpenSTEP-style text plists are also quick, but are deprecated and have limitations such as not supporting integer/boolean types.)
** Theos automatically converts plists to binary in release builds.
* Manage memory correctly as per the Cocoa memory management guidelines; ensure all hooks comply as well.
** Using automatic reference counting (ARC) by supplying the CFLAG <code>-fobjc-arc</code> is strongly recommended. This is included by default in modern Theos templates.
* Be aware that <code>-[NSBundle bundleIdentifier]</code> may return <code>nil</code> if it's invoked within an arbitrary process that does not belong to any specific bundle. When it's used with certain other methods that expect the non-null value, it can indefinitely crash the process. In some cases, it will drain the battery because the process will keep restarting. This is some [https://github.com/pr0crustes/SmoothTable/issues/2 bad example].
* Respect the user's privacy.
* Respect the user's privacy.
** The IMEI, telephone number, contacts list and email inbox are definitely private information.
** The IMEI, telephone number, contacts list and email inbox are definitely private information.
** The MAC address, Bluetooth address and UDID may be considered private information, depending on the jurisdiction.
** The MAC address, Bluetooth address and UDID may be considered private information, depending on the jurisdiction.
* Strip symbols if you have something to hide; use standard C (or C++) for the hidden parts--Objective-C will spill your details.
** Don’t bypass iOS privacy prompts ([[TCC.framework|TCC]]). Allow users to make privacy decisions for themselves.
* Many private frameworks have public headers available for Mac OS X; use them (also, there are header packs floating around--kennytm's is one of the best and is legal to distribute.)
 
* Only link to the frameworks you actually need--importing unnecessary frameworks will increase app launch time (use dlopen/dlsym to load libraries and access symbols at runtime after the app has launched)
== Data management ==
** This is less of a concern on 3.1+, due to the [[dyld_shared_cache|dyld shared object cache]]. You still incur the overhead of library initializers, however.
 
* Use a private prefix on classes and category methods: Objective-C has NO namespacing capabilities. (examples: two classes called Preferences that behave differently; two extensions define a -[NSData base64String] that behave slightly differently.)
* Use the CFPreferences/NSUserDefaults/HBPreferences system to ensure your preferences are stored correctly/efficiently, and are backed up to iCloud/iTunes. This is where [[Preferences.framework]] saves to by default.
* Store preferences in /var/mobile/Library/Preferences to have iTunes back it up.
* Store user data in /var/mobile/Library/Preferences to avoid sandbox restrictions, if necessary.
* Store data in /var/mobile/Library/AddressBook or /var/mobile/Library/Keyboard to avoid the sandbox restrictions, if necessary.
* Don’t include a “default” version of your preferences file in your package layout; this would overwrite the user’s settings when they update/reinstall your package. For NSUserDefaults/HBPreferences, you can use <code>-registerDefaults:</code> to set your default preferences.
* Avoid waking the device or keeping the device from sleeping. If your extension needs to do something periodically, hook the Mail application's sync and integrate the action within that (scheduler/alarm extensions need not heed this warning.)
 
* Avoid CPU-/GPU-/disk-intensive activity where the user would not expect it (battery life!)
== MobileSubstrate APIs and states ==
* Don't overuse NSLog/CFLog/fprintf(stderr, …); they're slow and synchronous.
 
* If your extension exposes an icon, include both the 29x29 and 59x60 icon sizes (more for iPad.)
* Always include a [[MobileSubstrate#MobileLoader|MobileLoader]] filter plist.
** If you require access to only SpringBoard, use "com.apple.springboard"; if you require access to all UIKit apps, use "com.apple.UIKit". However, read the next bullet.
** If you hook a framework like "com.apple.UIKit", test your tweak in non-UIKit processes anyway (e.g. by temporarily changing the filter to "com.apple.Security" or "com.apple.CoreFoundation"). Broken tweaks with incorrect/missing filters will pull your tweak into processes you don't expect. Even the recent versions of Cydia Substrate reject the tweaks without the filter file, tweaks targeting UIKit can still run in such processes you don't expect. However, there is a way to prevent unwanted code injection to non-UIKit processes. For more filtering options (for example, injection into keyboard extensions), see [https://github.com/PoomSmart/EmojiPort-PE/blob/master/Tweak.x here]. The full macros are available [https://github.com/PoomSmart/PSHeader/blob/master/PS.h here].
* Don't patch files that belong to the OS, jailbreak, or any app/package; use runtime replacement instead.
* If your extension exposes an API, document it!
** Include headers inside your package so they're easy to find, or put them in an obvious place in your project’s GitHub repository.
* Use an established hooking framework such as substrate.h, CaptainHook.h or [[Logos]]; there is little benefit in rolling your own, and too many details have to be just right.
** Even when using Logos, use the default <code>MobileSubstrate</code> generator; avoid using <code>internal</code>, at least on iOS.
* Never inhibit MobileSubstrate's [[MobileSubstrate#Safe_mode|safe mode]].
* If your extension manipulates icon layouts, use [[IconSupport]] so you don't stomp all over other extensions' modifications or the stock/safe-mode layout.
** Without IconSupport, your custom layout may be destroyed by SpringBoard when entering safe mode.
* Expect others to hook the same methods you do; expect them not to have read this list and to have done crazy things.
* Expect others to hook the same methods you do; expect them not to have read this list and to have done crazy things.
* Prefer hooking methods over hooking functions--new jailbreaks don't always have kernel patches (although this is not as much of a concern, as jailbreakers understand the need for W^X) and MobileSubstrate may not support newer ARM instructions.
* Prefer hooking methods over hooking functions--there are some notable limitations to function hooking, sometimes varying between jailbreaks, which are not a concern when hooking Objective-C methods.
* Avoid changing the class of objects returned by public APIs--some App Store applications perform unnecessary checks against the class of an object and will fail.
* Use [http://www.cydiasubstrate.com/api/c/MSHookMessageEx/ MSHookMessageEx] instead of [http://www.cydiasubstrate.com/api/c/MSHookMessage/ MSHookMessage].
* Prefer binary plists over XML--they're much quicker (OpenSTEP-style text plists are also quick, but seem to be deprecated)
* Manage memory correctly as per the Cocoa memory management guidelines; ensure all hooks comply as well.
* Use MSHookMessageEx instead of MSHookMessage.
** MSHookMessage allows you to specify an optional prefix which gets added to the original method, polluting the class.
** MSHookMessage allows you to specify an optional prefix which gets added to the original method, polluting the class.
== Packaging ==
* When preparing a build for release, if using Theos, add <code>FINALPACKAGE=1</code> to your make command line so it creates a production/release build. This enables optimizations, strips symbols, and removes the build and <code>+debug</code> tags from the version number.
* Include the proper Depends: line in your Debian control file (that way Cydia will automatically install dependencies and will alert users when they are missing required components like firmware version.)
** If your package includes a Substrate extension (tweak dylib), your Depends should include <code>mobilesubstrate</code>.
** If your package includes a preference bundle, your Depends should include <code>preferenceloader</code>.
** If your package depends on a framework such as Cephei, your Depends should include <code>ws.hbang.common</code>.
** Optionally, you can block installation on older or newer iOS releases using version ranges on the firmware metapackage. <code>firmware (>= 13.0)</code> would block installation unless the device is running iOS 13.0 or newer; this can also be combined with an upper bound such as <code>firmware (<< 15.0)</code> to block installation on iOS 15.0 or newer.
* Ensure your extension is localizable; use the standard .lproj system where possible. Avoid having images that include text.

Latest revision as of 13:14, 29 May 2021

Follow the following guidelines to avoid common MobileSubstrate mistakes. See also: Best Practices.

Coding practices

  • You generally should not use UIKit classes in a constructor function, as many of them rely on UIApplication already being initialized first. Using UIDevice too early can cause odd system behavior.
  • Access ivars through their associated properties, where practical.
  • Always call the old implementation of a method unless you require suppressing the default behavior. Similarly, only call the old implementation of a method from within that method’s hook.
  • Prefer to use documented/public SDK features when they exist; don’t use private APIs just because you can. Public APIs are guaranteed to always work, and won’t be removed in future releases.
  • Avoid UIAlertView or UIAlertController in SpringBoard--it doesn't participate in the SpringBoard-wide alert system; use a custom SBAlertItem subclass instead (not necessary for cases where you don't care if the system destroys your alert.)
  • Use Auto Layout to make your extension resolution-independent and rotation-aware. Using -autoresizingMask and -layoutSubviews may also be acceptable in some situations. However, be aware that -layoutSubviews can be invoked frequently, causing poor performance if your code is not well optimized.
  • Avoid doing extra work--the methods your extension hooks may be called from inside a tight loop; disk access is considered a LOT of extra work (related: properties are actually method calls, they can be slow.)
  • UI* methods must be performed on the main thread. This includes UIImage! Use CGImageSource and company if you need to load images on a background thread.
  • Never block the main thread! Performing any operation that can take significant time on the main thread is evil (that includes any operation that hits the network.)
  • Access resources through NSBundle so that they may be themed via Anemone, SnowBoard, and WinterBoard.
  • Don't make assumptions about the ivar layout of a class; these can and will change across firmware versions. Use MSHookIvar or -valueForKey: instead.
  • If accessing low-level graphics hardware, beware that first-generation devices do _not_ have a unified memory model (VRAM and regular RAM are separate regions.)
  • Participate in the memory warning system if your extension has a significant memory footprint; release everything you can.
  • Use mmap() when working with large datasets--the kernel will swap it in and out of memory as necessary (but avoid heavy writes, as that's no better than virtual memory.)
  • Decide how far back you want your package to support, and set the minimum deployment version to that. If you need to use newer APIs than your minimum version supports, use feature detection. In modern projects, this can be done using the special if (@available(…)) macro, otherwise you can use version.h from Theos’s version.h.
  • Strip symbols if you have something to hide; use standard C (or C++) for the hidden parts--Objective-C will spill your details.
  • Many private frameworks have public headers available for macOS; use them. Theos includes a few private headers that fill some of the gaps.
  • Only link to the frameworks you actually need--importing unnecessary frameworks will increase app launch time, and can trigger frequent daemon crashes on iOS 13+. Use NSBundle or dlopen/dlsym to load libraries and access symbols at runtime after the app has launched.
  • Use a unique prefix on classes and category/%new methods: Objective-C has NO namespacing capabilities. (examples: two classes called Preferences that behave differently; two extensions define a -[NSData base64String] that behave slightly differently.) Examples: ABCDMyClass for a class, abcd_myMethod for a category method.
  • Avoid waking the device or keeping the device from sleeping. If your extension needs to do something periodically, use PersistentConnection to ensure the system wakes up the CPU and radios for you (if your timer needs to happen at an exact time), or executes it the next time the system wakes up (if your timer being delayed doesn’t matter).
  • Avoid CPU-/GPU-/disk-intensive activity where the user would not expect it (battery life!)
  • Don't overuse NSLog(), CFLog(), fprintf(stderr, …); they're slow and synchronous.
  • If your extension includes a preference bundle, make sure to include an icon. These should be: icon.png at 29x29, [email protected] at 58x58, [email protected] at 87x87. This will be displayed at the root Settings app screen, so it is critical to have an icon, and match the Settings icon size convention.
  • Avoid changing the class of objects returned by public APIs--some App Store applications perform unnecessary checks against the class of an object and will fail.
  • Prefer binary plists over XML--they're much quicker (OpenSTEP-style text plists are also quick, but are deprecated and have limitations such as not supporting integer/boolean types.)
    • Theos automatically converts plists to binary in release builds.
  • Manage memory correctly as per the Cocoa memory management guidelines; ensure all hooks comply as well.
    • Using automatic reference counting (ARC) by supplying the CFLAG -fobjc-arc is strongly recommended. This is included by default in modern Theos templates.
  • Be aware that -[NSBundle bundleIdentifier] may return nil if it's invoked within an arbitrary process that does not belong to any specific bundle. When it's used with certain other methods that expect the non-null value, it can indefinitely crash the process. In some cases, it will drain the battery because the process will keep restarting. This is some bad example.
  • Respect the user's privacy.
    • The IMEI, telephone number, contacts list and email inbox are definitely private information.
    • The MAC address, Bluetooth address and UDID may be considered private information, depending on the jurisdiction.
    • Don’t bypass iOS privacy prompts (TCC). Allow users to make privacy decisions for themselves.

Data management

  • Use the CFPreferences/NSUserDefaults/HBPreferences system to ensure your preferences are stored correctly/efficiently, and are backed up to iCloud/iTunes. This is where Preferences.framework saves to by default.
  • Store user data in /var/mobile/Library/Preferences to avoid sandbox restrictions, if necessary.
  • Don’t include a “default” version of your preferences file in your package layout; this would overwrite the user’s settings when they update/reinstall your package. For NSUserDefaults/HBPreferences, you can use -registerDefaults: to set your default preferences.

MobileSubstrate APIs and states

  • Always include a MobileLoader filter plist.
    • If you require access to only SpringBoard, use "com.apple.springboard"; if you require access to all UIKit apps, use "com.apple.UIKit". However, read the next bullet.
    • If you hook a framework like "com.apple.UIKit", test your tweak in non-UIKit processes anyway (e.g. by temporarily changing the filter to "com.apple.Security" or "com.apple.CoreFoundation"). Broken tweaks with incorrect/missing filters will pull your tweak into processes you don't expect. Even the recent versions of Cydia Substrate reject the tweaks without the filter file, tweaks targeting UIKit can still run in such processes you don't expect. However, there is a way to prevent unwanted code injection to non-UIKit processes. For more filtering options (for example, injection into keyboard extensions), see here. The full macros are available here.
  • Don't patch files that belong to the OS, jailbreak, or any app/package; use runtime replacement instead.
  • If your extension exposes an API, document it!
    • Include headers inside your package so they're easy to find, or put them in an obvious place in your project’s GitHub repository.
  • Use an established hooking framework such as substrate.h, CaptainHook.h or Logos; there is little benefit in rolling your own, and too many details have to be just right.
    • Even when using Logos, use the default MobileSubstrate generator; avoid using internal, at least on iOS.
  • Never inhibit MobileSubstrate's safe mode.
  • If your extension manipulates icon layouts, use IconSupport so you don't stomp all over other extensions' modifications or the stock/safe-mode layout.
    • Without IconSupport, your custom layout may be destroyed by SpringBoard when entering safe mode.
  • Expect others to hook the same methods you do; expect them not to have read this list and to have done crazy things.
  • Prefer hooking methods over hooking functions--there are some notable limitations to function hooking, sometimes varying between jailbreaks, which are not a concern when hooking Objective-C methods.
  • Use MSHookMessageEx instead of MSHookMessage.
    • MSHookMessage allows you to specify an optional prefix which gets added to the original method, polluting the class.

Packaging

  • When preparing a build for release, if using Theos, add FINALPACKAGE=1 to your make command line so it creates a production/release build. This enables optimizations, strips symbols, and removes the build and +debug tags from the version number.
  • Include the proper Depends: line in your Debian control file (that way Cydia will automatically install dependencies and will alert users when they are missing required components like firmware version.)
    • If your package includes a Substrate extension (tweak dylib), your Depends should include mobilesubstrate.
    • If your package includes a preference bundle, your Depends should include preferenceloader.
    • If your package depends on a framework such as Cephei, your Depends should include ws.hbang.common.
    • Optionally, you can block installation on older or newer iOS releases using version ranges on the firmware metapackage. firmware (>= 13.0) would block installation unless the device is running iOS 13.0 or newer; this can also be combined with an upper bound such as firmware (<< 15.0) to block installation on iOS 15.0 or newer.
  • Ensure your extension is localizable; use the standard .lproj system where possible. Avoid having images that include text.