Getting Started
Preface
If you are using Orion for tweak development, it is recommended that you use it with Theos. This guide assumes that you have Theos installed; if you haven’t done that yet, please follow the installation instructions for Theos and then return to this guide.
If you wish to use Orion without Theos, please refer to the “Using Orion Without Theos” guide.
This guide will show you how to make a simple Orion tweak which… spices up text labels a little. We will target the VLC Media Player iOS app since it is open source and supports a large range of iOS versions, however most other apps should work too.
To follow along, you will require the following things:
- Theos on a machine running macOS, Windows 10 with WSL, or Linux. If you’re in a Linux environment, you must install a host Swift toolchain in addition to a Swift-compatible iOS toolchain.
- A jailbroken iOS device.
- The target app (in this tutorial, VLC for iOS) installed on your iOS device.
Note that this guide will use the format
<current directory> $ <command>
for all shell commands. User input will be in red
. Some text may be truncated using [...]
.
Setup
- Add the Chariz repo (https://repo.chariz.com/) to your jailbroken device’s package manager.
- Install the Orion Runtime package (pick iOS 12-13 or iOS 14-16 depending on your version).
Initializing an Orion Tweak
Theos comes with a tweak_swift
template which uses Orion.
To get started, run the New Instance Creator nic.pl
. Enter the template number corresponding to iphone/tweak_swift
. Pick a name and bundle identifier for your tweak. Provide VLC’s bundle ID: org.videolan.vlc-ios
, as well as the full name of the VLC app in single quotes.
~ $ nic.pl NIC 2.0 - New Instance Creator ------------------------------ [1.] iphone/activator_event [2.] iphone/activator_listener [...] [16.] iphone/tweak [17.] iphone/tweak_swift [18.] iphone/tweak_with_simple_preferences [19.] iphone/xpc_service Choose a Template (required): 17 Project Name (required): My Tweak Package Name [com.yourcompany.mytweak]: com.kabiroberai.mytweak Author/Maintainer Name [Your Name]: Kabir Oberai [iphone/tweak_swift] MobileSubstrate Bundle filter [com.apple.springboard]: org.videolan.vlc-ios [iphone/tweak_swift] List of applications to terminate upon installation (space-separated, '-' for none) [SpringBoard]: 'VLC for iOS' Instantiating iphone/tweak_swift in mytweak/... Done.
NIC will create a directory with the same name as your tweak. cd
into this directory and take a minute to look around. The file structure looks like this:
- Makefile
- MyTweak.plist
- control
- Package.swift
- Sources
- MyTweak
- Tweak.x.swift
- MyTweakC
- [...]
The number of files might seem daunting at first, but each one has a specific purpose which you’ll quickly come to learn. Here’s a brief description:
Makefile
: This file is the keystone of any Theos project. It describes how your tweak should be built, including the list of source files as well as the flags that should be passed to the compiler.MyTweak.plist
: This is the CydiaSubstrate bundle filter for your tweak. It tells CydiaSubstrate (or an equivalent tweak loader) which processes your tweak should be loaded into.control
: The Debian control file which describes your tweak’s .deb package to package managers.Package.swift
: This file is not used by Theos itself, but it provides SourceKit with an equivalent description of your project in order to enable code completion.Sources
: This is where your source code goes. C/Objective-C files (.m
,.mm
,.c
,.cpp
) go in the folder with theC
suffix, and Swift/Orion files (.swift
,.x.swift
) go into the folder without the suffix. Theos will automatically find these files while building your tweak, so you need not manually specify them in theMakefile
.
Editing your Tweak
Orion uses a preprocessor, and Theos passes all .x.swift
files through this preprocessor while building. All hooks must therefore go into files with a .x.swift
extension. The template creates a Tweak.x.swift
for you, so we’ll start by editing this.
Now, before you fire up your favorite text editor and start hammering out code in Sources/MyTweak/Tweak.x.swift
, Theos has a trick up its sleeve: with some basic configuration, you can get full code completion while working on Orion tweaks on any OS, in most editors! Enabling this is simple:
- Install the sourcekit-lsp plugin corresponding to your editor of choice. (If you’re using Xcode, it has built-in SourceKit support so skip this step.)
- Run
make spm
to generate metadata about your project, which is needed by Swift Package Manager/SourceKit. - Open the project folder in your chosen editor.
Just like that, you should have code completion! Now drill down to Sources/MyTweak/
and select Tweak.x.swift
. Delete the contents of the file and replace them with the following:
// 1
import Orion
import UIKit
// 2
class LabelHook: ClassHook<UILabel> {
}
Here’s an explanation of the important lines:
- We first import Orion’s APIs into the Swift file using
import Orion
. We also importUIKit
since we need the compiler to know about theUILabel
class. - Next, we declare a hook. A hook is the fundamental unit of Orion tweaks. It is used to modify the behavior of existing code. A
ClassHook
allows you to replace methods on a “target” class (in this case,UILabel
) by writing own implementations.
Next, insert the following code between the curly braces (right before the last line):
// 1
func setText(_ text: String) {
// 2
orig.setText(
// 3
text.uppercased().replacingOccurrences(of: " ", with: "👏")
)
}
Here’s a breakdown of this code:
- In brief, any function declared within a class hook replaces (“swizzles”) the implementation of the Objective-C function with the same name in the target class. In this case, we are therefore changing the behavior of the
setText
function ofUILabel
, which is called whenever thetext
property is set. In Objective-C land, this is called a setter. - In our replaced implementation, we call back to the original implementation of
setText
but we replace the argument with our own. The original implementations of our swizzled methods can be accessed viaorig
as shown in the above code. - The replaced argument is the text in all caps with all spaces replaced with the 👏 emoji.
All in all, your code should look like this:
import Orion
import UIKit
class LabelHook: ClassHook<UILabel> {
func setText(_ text: String) {
orig.setText(
text.uppercased().replacingOccurrences(of: " ", with: "👏")
)
}
}
That’s all for the code! You can now build, package, and install the tweak in one go:
~/mytweak $ make do > Making all for tweak MyTweak… ==> Preprocessing Sources/MyTweak/Tweak.x.swift… ==> Preprocessing Sources/MyTweak/Tweak.x.swift… ==> Compiling Sources/MyTweak/Tweak.x.swift (arm64e)… ==> Compiling Sources/MyTweak/Tweak.x.swift (arm64)… ==> Generating MyTweak-Swift.h (arm64)… ==> Generating MyTweak-Swift.h (arm64e)… ==> Compiling Sources/MyTweakC/Tweak.m (arm64e)… ==> Compiling Sources/MyTweakC/Tweak.m (arm64)… ==> Linking tweak MyTweak (arm64)… ==> Linking tweak MyTweak (arm64e)… ==> Generating debug symbols for MyTweak… ==> Generating debug symbols for MyTweak… ==> Merging tweak MyTweak… ==> Signing MyTweak… > Making stage for tweak MyTweak… dm.pl: building package `com.kabiroberai.mytweak:iphoneos-arm' in `./packages/com.kabiroberai.mytweak_0.0.1-1+debug_iphoneos-arm.deb' ==> Installing… (Reading database ... 3540 files and directories currently installed.) Preparing to unpack /tmp/_theos_install.deb ... Unpacking com.kabiroberai.mytweak (0.0.1-1+debug) ... Setting up com.kabiroberai.mytweak (0.0.1-1+debug) ... ==> Unloading 'VLC for iOS'…
Finally, open up the VLC app on your device. You should see that all (or most) text labels have become uppercase with 👏 emojis instead of spaces.
Bonus Challenge
Can you change the text to sPonGEboB cAsE? Try making the characters alternate between upper- and lowercase, or randomize whether each character is upper- or lowercase. Optionally, force certain letters to be either lowercase or uppercase; for example, try making it so the letter “L” is always uppercase and the letter “i” is always lowercase to avoid ambiguity.
Solution
Here’s one way to achieve this:import Orion
import UIKit
class LabelHook: ClassHook<UILabel> {
// a dictionary representing characters that should always
// map to a specific corresponding value
private static let mappings: [Character: Character] = [
"i": "i", "I": "i",
"l": "L", "L": "L",
" ": "👏"
]
func setText(_ text: String) {
var modifiedText = ""
for char in text {
if let mapping = LabelHook.mappings[char] {
modifiedText.append(mapping)
} else if Bool.random() {
modifiedText += char.lowercased()
} else {
modifiedText += char.uppercased()
}
}
orig.setText(modifiedText)
}
}
Bonus-bonus-challenge: add support for languages besides English :)