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.
  • 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

  1. Add the Chariz repo (https://repo.chariz.com/) to your jailbroken device’s package manager, and install the Orion Runtime package (pick iOS 12-13 or iOS 14 depending on your version).
  2. Inside your Theos installation directory ($THEOS), switch to the orion branch by running git fetch && git checkout orion && git submodule update --init.

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 the C 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 the Makefile.

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:

  1. 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.)
  2. Run make spm to generate metadata about your project, which is needed by Swift Package Manager/SourceKit.
  3. 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:

  1. We first import Orion’s APIs into the Swift file using import Orion. We also import UIKit since we need the compiler to know about the UILabel class.
  2. 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:

  1. 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 of UILabel, which is called whenever the text property is set. In Objective-C land, this is called a setter.
  2. 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 via orig as shown in the above code.
  3. 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.

Tweaked VLC for iOS app

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 :)