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
- A machine running macOS, Windows 10 with WSL, or Linux. If using WSL or Linux, an iOS Swift toolchain is also required.
- A jailbroken iOS device with Orion installed.
- 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 [...]
.
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 helps provide Xcode with a description of your tweak’s files and compiler flags which is similar to that provided to Theos by the Makefile. This allows you to edit your tweak using the Xcode IDE, with full code completion and whatnot. You’ll want to keep this in sync with any changes you make to your Makefile.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
.
For more details about the Theos side of things, see Advanced Theos Usage.
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, while one option is to open Sources/MyTweak/Tweak.x.swift
in your favorite text editor, Orion offers an even better option on macOS: editing your tweak in Xcode!
Opening your tweak in Xcode is simple:
~/mytweak $ make dev
This should open Xcode with your tweak as a Swift Package. 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 (tailored towards English-based locales):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)
}
}