Doing tweak settings the right-er way

Posted on Feb 15, 2015 by kirb — Comments

As it turned out, retrieving tweak settings from NSUserDefaults as outlined in the post I wrote a few months ago proved to not be very robust and still had problems within a sandboxed process.

At one point several months prior to the libcephei update a few weeks ago, I thought about how preferences loading could be improved, and started working on a class called HBPreferences. The idea is that either you keep an instance of this class as a global variable in your tweak, and use it basically as you would with NSUserDefaults. Or, you can take it one step further than what NSUserDefaults is capable of and “register” a variable’s pointer so it’ll always be up to date with no preference reloading code required in your tweak.

If you don’t already understand the changes made in iOS 8, refer to the first few paragraphs of the original post.

Setting up libcephei with Theos

You will need to copy libcephei’s headers and libraries to your development machine so the compiler and linker respectively are aware of it.

You won’t find libcephei in Cydia by searching for “Cephei” because it’s been set as hidden. However, you can install a package that depends on it like TypeStatus, or install “APT 0.7 Strict” (apt7) if you don’t already have it and run:

apt-get install ws.hbang.common

On your development machine, copy the required files like so:

THEOS=/opt/theos  # if you don't already have $THEOS set, set it to the location of theos
THEOS_DEVICE_IP=iphone  # and set these two if you haven't already
THEOS_DEVICE_PORT=22

scp -P $THEOS_DEVICE_PORT root@$THEOS_DEVICE_IP:/usr/lib/libcephei\* $THEOS/lib
scp -r -P $THEOS_DEVICE_PORT root@$THEOS_DEVICE_IP:/usr/include/\{Cephei,CepheiPrefs\} $THEOS/include

Now, open your project’s makefile and add:

TargetName_LIBRARIES += cephei

In your control file, add ws.hbang.common to your dependencies, and set it to require the latest version or newer. At the time of writing, that’s version 1.2, so for example:

Depends: mobilesubstrate, ws.hbang.common (>= 1.2)

NSUserDefaults compatible method

This method has almost no changes from the example on the original post - just replace NSUserDefaults with HBPreferences and initialise with the initWithIdentifier: method like so:

static NSString *const kHBCBPreferencesDomain = @"ws.hbang.cobalia";
static NSString *const kHBCBPreferencesEnabledKey = @"Enabled";
static NSString *const kHBCBPreferencesSwitchesKey = @"Switches";
static NSString *const kHBCBPreferencesSectionLabelKey = @"SectionLabel";
static NSString *const kHBCBPreferencesSwitchLabelsKey = @"SwitchLabels";

HBPreferences *preferences;

%ctor {
	preferences = [[HBPreferences alloc] initWithIdentifier:kHBCBPreferencesDomain];

	[preferences registerDefaults:@{
		kHBCBPreferencesEnabledKey: @YES,
		kHBCBPreferencesSwitchesKey: @[ /* ... */ ],
		kHBCBPreferencesSectionLabelKey: @YES,
		kHBCBPreferencesSwitchLabelsKey: @YES
	}];
}

I use constants for strings that shouldn’t ever change - you don’t need to but I’d recommend it. If you prefer, you can also set keys on preferences.defaults directly, as it’s an NSMutableDictionary.

Once again, it’s as simple as [preferences objectForKey:kHBCBPreferencesEnabledKey] to get an Objective-C object (or nil if there’s no value and no default registered), or get a primitive directly using any of bool, double, integer, or floatForKey:.

Legacy library support

As mentioned in the previous post, at the time that Cobalia was written, Flipswitch was not yet updated to support the new preferences system. Here is how it was fixed - by reading the plist from the disk and copying the data into the in-memory preferences cache:

void HBCBPreferencesChanged() {
	NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:[[[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"Preferences"] stringByAppendingPathComponent:kHBCBPreferencesDomain] stringByAppendingPathExtension:@"plist"]];

	if (plist[kHBCBPreferencesSwitchesKey]) {
		[preferences setObject:plist[kHBCBPreferencesSwitchesKey] forKey:kHBCBPreferencesSwitchesKey];
	}
}

%ctor {
	HBCBPreferencesChanged();
	CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, (CFNotificationCallback)HBCBPreferencesChanged, CFSTR("ws.hbang.cobalia/ReloadPrefs"), NULL, kNilOptions);
}

Obviously you’ll also need to set the appropriate key in your preference specifiers for a Darwin notification to be posted with the name you provide.

Variable registration

Finally, I want to explain the most powerful feature of HBPreferences: being able to “register” a variable so that its value is always kept up to date. This is quite easy to do:

BOOL enabled;
NSArray *switches;
BOOL sectionLabel, switchLabel;

%ctor {
	HBPreferences *preferences = [HBPreferences preferencesWithIdentifier:@"ws.hbang.cobalia"];

	[preferences registerBool:&enabled default:YES forKey:@"Enabled"];
	[preferences registerObject:&switches default:@[ /* ... */ ] forKey:@"Switches"];
	[preferences registerBool:&sectionLabel default:YES forKey:@"SectionLabel"];
	[preferences registerBool:&switchLabel default:YES forKey:@"SwitchLabels"];
}

In your preference specifiers, ensure you have the PostNotification key set to the identifier you pass in to HBPreferences, followed by /ReloadPrefs. For example:

<dict>
	<key>cell</key>
	<string>PSSwitchCell</string>
	<key>default</key>
	<true/>
	<key>defaults</key>
	<string>ws.hbang.cobalia</string>
	<key>key</key>
	<string>Enabled</string>
	<key>label</key>
	<string>Enabled</string>
	<key>PostNotification</key>
	<string>ws.hbang.cobalia/ReloadPrefs</string>
</dict>

And that’s it - now all you need to do is refer to these variables as you always would. You don’t need to worry at all about what happens when the user changes a setting; HBPreferences takes care of it for you and updates your variables.

If you want to learn more about what libcephei can do, take a look at its documentation.

Setting up SSH via USB connection on Linux

Posted on Feb 14, 2015 by CoolStar — Comments

This is a version of Aehmlo’s original post, modified for Linux.

When developing tweaks (or making themes, for that matter), it is often annoying to wait for files to copy (and commands to execute) over Wi-Fi - it tends to be very slow and sometimes unreliable, and one must keep track of IP addresses and such (even if they use a hosts file to map custom hostnames) in order to accomplish it. This annoyance can be greatly relieved by creating a local tunnel over a USB connection to the target device, and using that to SSH to the device much more quickly and reliably. In this tutorial, we will cover how to set up your Linux PC (there are other posts for Windows and OS X) so that port 2222 is forwarded to port 22 on whatever device is plugged in.

First things first, make sure you have a recent version of libimobiledevice installed, as well as its utilities. On Debian and Ubuntu, the package name is libimobiledevice-utils.

In a terminal, run the following command to start the tunneling:

iproxy 2222 22

That’s it!

Having this run all the time in the background is different depending on what daemon system your distro uses. If your distro uses Upstart, such as Ubuntu, create a file as root at /etc/init/iproxy.conf:

sudo nano /etc/init/iproxy.conf

Enter the following:

start on runlevel [2345]
stop on runlevel [!2345]

setuid nobody
setgid nogroup

exec /usr/bin/iproxy 2222 22

Use sudo start iproxy to start it without having to reboot.

Take a look at the original OS X post to find out how to use this with Theos, and to prevent a false security error if you plug in a different device.

Setting up SSH via USB connection on Windows

Posted on Feb 14, 2015 by CoolStar — Comments

This is a version of Aehmlo’s original post, modified for Windows.

When developing tweaks (or making themes, for that matter), it is often annoying to wait for files to copy (and commands to execute) over Wi-Fi - it tends to be very slow and sometimes unreliable, and one must keep track of IP addresses and such (even if they use a hosts file to map custom hostnames) in order to accomplish it. This annoyance can be greatly relieved by creating a local tunnel over a USB connection to the target device, and using that to SSH to the device much more quickly and reliably. In this tutorial, we will cover how to set up your Windows PC (there are other posts for Linux and OS X) so that port 2222 is forwarded to port 22 on whatever device is plugged in.

First things first, download the client from its download page (note that you will need Windows XP and iTunes 10.5 or later for the client to work). Extract this zip - make sure you extract both the executable and the dll.

In Command Prompt, cd to the folder you extracted the zip to and run the following:

itunnel_mux --iport 22 --lport 2222

From now on, the relay we have set up will always be running the background once you log in. Try it out in PuTTY by connecting to SSH at localhost, port 2222.

If you use Cygwin, you can easily register this as a Windows service so it’s always running. You will need to use the Cygwin setup from cygwin.com to install cygrunsrv, under the Admin category. Now, from a Cygwin shell run as an administrator:

cygrunsrv -I iTunnel -p /cygdrive/c/path/to/itunnel_mux.exe -a '--iport 22 --lport 2222' -u 'NETWORK SERVICE' -y 'Apple Mobile Device'
net start itunnel

This creates a service with the name itunnel, which will run itunnel_mux.exe with the provided arguments. It’ll execute as Windows’ built in NetworkService account, and requires the Apple Mobile Device service to be up and running before iTunnel can start.

When you update Cygwin packages, you should execute net stop itunnel (again, as an administrator) before the update and net start itunnel after. Otherwise, you may be told to restart your computer to replace in-use files.

Take a look at the original OS X post to find out how to use this with Theos, and to prevent a false security error if you plug in a different device.

Setting up SSH via USB connection on OS X

Posted on Dec 28, 2014 by Aehmlo — Comments

When developing tweaks (or making themes, for that matter), it is often annoying to wait for files to copy (and commands to execute) over Wi-Fi - it tends to be very slow and sometimes unreliable, and one must keep track of IP addresses and such (even if they use a hosts file to map custom hostnames) in order to accomplish it. This annoyance can be greatly relieved by creating a local tunnel over a USB connection to the target device, and using that to SSH to the device much more quickly and reliably. In this tutorial, we will cover how to set up your Mac (not PC, sorry - I am not knowledgable enough to write on this) so that port 2222 is forwarded to port 22 on whatever device is plugged in. This service will be started automatically and will run in the background at all times, out of sight and out of mind.

First things first, download the client from its download page (note that you will need iTunes 10.5 or later for the client to work). Extract this zip, and move the extracted contents to ~/Library/Application Support/usbmuxd/ (or something else, if you wish, but make sure to change the path in the plist accordingly) - both tunl and libmd.dylib.

Now, create a new file named net.sharedinstance.tcprelay.plist in ~/Library/LaunchAgents. Inside this file, put the following:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>net.sharedinstance.tcprelay</string>
	<key>KeepAlive</key>
	<dict>
		<key>NetworkState</key>
		<true/>
	</dict>
	<key>ProgramArguments</key>
	<array>
		<string>/Users/USER/Library/Application Support/usbmuxd/itnl</string>
		<string>--iport</string>
		<string>22</string>
		<string>--lport</string>
		<string>2222</string>
	</array>
	<key>RunAtLoad</key>
	<true/>
</dict>
</plist>

Be sure to replace USER above with your username (and if you put the executable elsewhere, make sure to change the first item in the ProgramArguments array to reflect that). Make sure that the itnl executable is, well, executable. Then, give the plist appropriate permissions - chmod 0644 ~/Library/LaunchAgents/net.sharedinstance.tcprelay.plist, and load this launch agent we have created - launchctl load ~/Library/LaunchAgents/net.sharedinstance.tcprelay.plist.

From now on, the relay we have set up will always be running the background once you log in. Try it out now with ssh -p 2222 mobile@localhost.

If you use this with multiple devices, you’ll notice a problem: you’ll get a scary host key changed warning:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.

The trick to avoiding this is to set the known hosts file to /dev/null when you’re connecting to localhost:2222. Create ~/.ssh/config if you don’t already have it and add the following:

Host local
	User root
	HostName localhost
	Port 2222
	StrictHostKeyChecking no
	UserKnownHostsFile=/dev/null

You can now use ssh local no matter what device is plugged in.

To use this with Theos, you can export the IP to the host alias and the port to 2222:

export THEOS_DEVICE_IP=local THEOS_DEVICE_PORT=2222

It would be ideal to also put this in your shell’s profile script (~/.bash_profile, ~/.zshrc, etc) so it’s set by default and you don’t have to worry about it.

Plug in a jailbroken iOS device, copy your SSH key to it if you haven’t already…

ssh local 'mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys' < ~/.ssh/id_rsa.pub

…and enjoy the blazingly fast transfer speed! Isn’t this much better than boring ol’ Wi-Fi?

Doing tweak settings the right way

Posted on Nov 26, 2014 by kirb — Comments

Please refer to the revision of this post.

You might have noticed that tweak settings have suddenly started acting different in iOS 8. This is because the cfprefsd concept from OS X (as long ago as in 10.8 Mountain Lion) has been brought across to iOS 8. When you change a setting now, the dictionary is no longer committed to disk immediately - rather, it’s kept in memory by cfprefsd and only flushed to disk when the appropriate process (or cfprefsd itself) terminates. So with that in mind, how do you manage settings on iOS 8 now?

It’s actually really simple and I’d argue a thousand times better than the hack all of us were using before this. First, you need to keep an instance of NSUserDefaults hanging around, and register your default preferences:

static NSString *const kHBCBPreferencesDomain = @"ws.hbang.cobalia";
static NSString *const kHBCBPreferencesEnabledKey = @"Enabled";
static NSString *const kHBCBPreferencesSwitchesKey = @"Switches";
static NSString *const kHBCBPreferencesSectionLabelKey = @"SectionLabel";
static NSString *const kHBCBPreferencesSwitchLabelsKey = @"SwitchLabels";

NSUserDefaults *userDefaults;

%ctor {
	userDefaults = [[NSUserDefaults alloc] _initWithSuiteName:kHBCBPreferencesDomain container:[NSURL URLWithString:@"/var/mobile"]];

	[userDefaults registerDefaults:@{
		kHBCBPreferencesEnabledKey: @YES,
		kHBCBPreferencesSwitchesKey: @[ /* ... */ ]
		kHBCBPreferencesSectionLabelKey: @YES,
		kHBCBPreferencesSwitchLabelsKey: @YES
	}]
}

You’ll probably need to define this private init method in a category interface like so:

@interface NSUserDefaults (Private)

- (instancetype)_initWithSuiteName:(NSString *)suiteName container:(NSURL *)container;

@end

I use constants for strings that shouldn’t ever change - you don’t need to but I’d recommend it.

Now, it’s as simple as a [userDefaults boolForKey:@"Enabled"] to grab a boolean, or objectForKey: for an Objective-C object, or any of the other methods the class supports.

Easy! If you notice, you don’t even need to watch for a traditional Darwin notification, nor do you need to define PostNotification on your preferences specifiers. The important thing to note is you need to directly call the methods on NSUserDefaults where ever you need to get a preference value in your code. You shouldn’t store the preferences in separate global variables as you might have done traditionally since then you need a Darwin notification and a callback function to update them - ruining the simplicity of this solution.

But what about libraries like AppList or Flipswitch whose preferences API still write directly to the plist? The easiest thing you can do is a quick little trick to pass that back to cfprefsd to keep in memory. This is how I do it in Cobalia:

void HBCBPreferencesChanged() {
	NSDictionary *preferences = [NSDictionary dictionaryWithContentsOfFile:[[[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"Preferences"] stringByAppendingPathComponent:kHBCBPreferencesDomain] stringByAppendingPathExtension:@"plist"]];

	if (preferences[kHBCBPreferencesSwitchesKey]) {
		[userDefaults setObject:preferences[kHBCBPreferencesSwitchesKey] forKey:kHBCBPreferencesSwitchesKey];
	}
}

%ctor {
	HBCBPreferencesChanged();
	CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, (CFNotificationCallback)HBCBPreferencesChanged, CFSTR("ws.hbang.cobalia/ReloadPrefs"), NULL, kNilOptions);
}

Obviously you’ll also need to set the appropriate key in your preference specifiers for a Darwin notification to be posted with the name you provide.

If you’d like to see what else people are using for their tweaks, there are a few others you can explore that are listed on the iPhone Dev Wiki’s updating extensions for iOS 8 article. To me, this is the most simple and elegant, and almost exactly what you’d ideally be using anyway in a standard iOS or OS X application - but you’re of course allowed to use whichever looks best to you.

Adding simple toggles in the Settings root list

Posted on Apr 18, 2014 by kirb — Comments

I just wanted to post a quick trick you can use to show a toggle in the root Settings list view.

If you generate a new preference bundle project with NIC and open entry.plist, you’ll find this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>entry</key>
	<dict>
		<key>bundle</key>
		<string>TypeStatus</string>
		<key>cell</key>
		<string>PSLinkCell</string>
		<key>detail</key>
		<string>HBTSListController</string>
		<key>icon</key>
		<string>icon.png</string>
		<key>isController</key>
		<true/>
		<key>label</key>
		<string>TypeStatus</string>
	</dict>
</dict>
</plist>

Indeed, the cells shown in the root of Settings courtesy of PreferenceLoader are customizable. You could slap your whole settings panel there if you wanted (but that’s clearly not a good idea). What you could do, though, is use it for tweaks whose settings consist of nothing more than an “enabled” toggle.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>entry</key>
	<dict>
		<key>cell</key>
		<string>PSSwitchCell</string>
		<key>default</key>
		<true/>
		<key>defaults</key>
		<string>ws.hbang.typestatus</string>
		<key>icon</key>
		<string>TypeStatus.png</string>
		<key>label</key>
		<string>TypeStatus</string>
		<key>key</key>
		<string>Enabled</string>
		<key>PostNotification</key>
		<string>ws.hbang.typestatus/ReloadPrefs</string>
	</dict>
</dict>
</plist>

The icon can be dropped at /Library/PreferenceLoader/Preferences/TypeStatus.png. Unfortunately for themers, this means it can’t be themed with WinterBoard.

Ok, that’s easy enough, but I mention this because if one or two tweaks did this, it would look pretty odd. If more tweaks do this, it’ll look completely normal. And, as Ryan Petrich noted in his WWJC talk a few days ago, every tweak should have settings - even if it’s just a single on/off switch.

Debugging memory issues in Substrate tweaks

Posted on Feb 10, 2014 by bensge — Comments

Memory issues in MobileSubstrate tweaks are generally not very easy to debug. In the following text I’m explaining some useful tools for finding overreleases and leaks in your own tweaks.

One useful trick is to override - (void)dealloc in your subclasses and write a message to the syslog; that way you can make sure your objects are actually getting freed after usage. If dealloc is not getting called, you probably need to release that object once more. Keep in mind you should remove those logs for release builds of your tweaks. An easy way to do this is by adding this:

#ifndef DEBUG
#define NSLog
#endif

On top of your tweaks makefile, add DEBUG=1 to enable NSLogs, set it to zero or remove the line to disable all logs. You can also pass it to make at the command line, for instance: make package install DEBUG=1.

Another extremely useful trick if you experience crashes from use-after-free issues is to enable zombie objects. To enable those in any process you want (e.x. SpringBoard), ssh into your device, attach to the process with cycript -p processname first. Then declare _CFEnableZombies() in cycript like so:

_CFEnableZombies = new Functor(dlsym(RTLD_DEFAULT, "_CFEnableZombies"), "v");

Now you can enable zombie objects simply by calling _CFEnableZombies(). Open up a syslog and keep and eye on it while the memory crash happens. You’ll see a message like this one:

<Error>: *** -[UIWindow methodSignatureForSelector:]: message sent to deallocated instance 0x162a7730`

There we go! Now just search for the (in this example) methodSignatureForSelector: in your code and fix the memory crash!

Happy debugging!

Important: Update your tweaks to support arm64

Posted on Dec 24, 2013 by kirb — Comments

The iPhone 5s, iPad Air and iPad mini (2nd generation) both run on a completely new processor architecture: arm64. If you haven’t heard, this architecture is 64-bit, unlike the previous 32-bit architectures (armv6, armv7 and armv7s). Of course, these devices are still backwards compatible with the 32-bit architectures, but for 64-bit processes, dynamic libraries not compiled for arm64 will not be loaded into them.

However, updating your tweak to work on arm64 is fairly simple. It does come with one caveat, however: you are required to compile with the iOS 7.0 or newer SDK. If you’re not using Xcode 5 or newer to compile your tweaks, you must do so in order to fully support these devices.

Unfortunately, if you’re not using the official toolchain (included with Xcode) to build your tweaks, it is currently not possible to support arm64, since Apple is yet to release the source code for the open-source tools included with the Xcode 5 toolchain.

Before you start, be sure to update your code for arm64. Please also remember that Substrate is currently not updated for arm64, so you may want to hold off on doing this if you don’t have an arm64 device to test with.

The easy way

(If you don’t need to support iOS 4.2.1 or older)

The easiest way to fix this issue is simply to start using the iOS 7 SDK, and force theos to build your tweaks for both armv7 and arm64. If you have Xcode 5, you’ve most likely already completed the first part (if not, head to the App Store and update Xcode!). The second part is still fairly easy: open your project’s makefile and add this line above the first include:

ARCHS = armv7 arm64

Finally, download this build of the libsubstrate.dylib stub (contains armv6/armv7/armv7s/arm64 as well as i386 for OS X and x86_64 for iOS Simulator) and replace the existing file at $THEOS/lib/libsubstrate.dylib. That’s it!

The slightly more complex way

It may not be viable to compile only against the iOS 7 SDK, since that might mean dropping support for iOS versions before 4.3. If you have a preference bundle, wee app, or anything else that is loaded into an arm64 process, don’t forget that you’ll need to perform the same thing to its binaries too. There are tons of goodies available on the dev wiki for this situation - and also has tips for issues that you may come across.

How to build jailbreak packages for iOS on Windows

Posted on Dec 24, 2013 by kirb — Comments

Want to develop jailbreak packages, but don’t have a Mac? You could easily download a toolchain on your iPhone and build packages on there, but if you have a Windows computer, you could instead use the extra speed that a desktop CPU provides, thanks to coolstar’s fork of theos and toolchain for Windows. At least Windows XP is required for this.

Cygwin

Cygwin website

The toolchain requires Cygwin, an awesome piece of software that provides a Unix environment on Windows. Grab the appropriate setup program for your system architecture from cygwin.com/install.html and run it. You’ll be greeted by the standard setup welcome page, followed by a request for where to download packages from. You’ll want to stick with the default “Install from Internet”. Next your way through until you get a list of sources - you can pick any of them; preferably one closer to you. After downloading a list of packages, the setup maximizes to show you a full list of available packages:

Package list

Here’s what you’ll need to search for and install:

  • git (under Devel)
  • ca-certificates (under Net)
  • make (under Devel)
  • perl (under Perl)
  • python (under Python)
  • openssh (under Net)

Hit next two more times and let these packages, and the core Cygwin packages, install.

Toolchain

If you allowed it, the setup program dropped a shortcut to Cygwin Terminal on your desktop and/or Start menu. (If not, you can manually launch C:\cygwin\bin\mintty.exe.) Launch either one and you’ll be greeted with a command line (bash):

Cygwin terminal

(I’ve customised mine - yours will have a different font and size.)

From here, we’ll create the directory where theos will live in, and clone it from coolstar’s fork:

mkdir -p /opt
cd /opt
git clone -b windows git://github.com/coolstar/theos.git

Next up is the toolchain itself, which will take a while…

git clone -b x86_64 git://github.com/coolstar/iOSToolchain4Win.git theos/toolchain/windows/iphone

If your copy of Windows is not 64-bit, replace x86_64 with master.

SDK

Once that’s done, you’ll need to download an SDK. Legally, you can only do this by downloading an Xcode DMG image from Apple, so head to their developer downloads page (login required) and download an Xcode version of your choice - try Xcode 5 for the iOS 7 SDK and 4.6.3 for iOS 6.1. Meanwhile, you’ll need to download and install TransMac so that you can extract files from the DMG.

Once the download is done, open the file. From there, click the DMG name in the sidebar, then navigate your way through to Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs. Right click iPhoneOS6.1.sdk and choose “Copy To”.

Extracting SDK

(I’m using Xcode 4.4.1, but the instructions will always be the same.)

Here, you’ll want to enter the path to Cygwin (unless you changed it, that’s C:\cygwin on 32-bit Windows, or C:\cygwin64 on 64-bit Windows), followed by the path to theos’ SDK directory, and the directory name: opt\theos\sdks\iPhoneOS6.1.sdk. Hit OK and wait for the magic to happen.

Try it out!

Everything should work as you expect it to now. Let’s try building a test tweak. Before you do, though, you should add theos environment variables to your .bash_profile (or equivalent for your shell). Open C:\cygwin\home\...username...\.bash_profile in your favorite editor and add this on the very last line, replacing the device name with your own device’s, replacing spaces with dashes, or its IP address:

export THEOS=/opt/theos
export THEOS_DEVICE_IP=kirbpad.local THEOS_DEVICE_PORT=22

Load this into the shell with . ~/.bash_profile, or close the terminal window and launch a new one. cd to where you would like to store your theos projects (note that your C: drive lives at /cygdrive/c under Cygwin), or just create a new directory for that under your cygwin home directory:

cd
mkdir projects
cd projects

Now run $THEOS/bin/nic.pl to summon the NIC. Select a tweak and provide the rest of the info. Now open Tweak.xm and paste in:

%ctor {
	NSLog(@"It works!");
}

Get ready to watch your syslog, and run make package install inside the project directory. If all goes well, you’ll see this somewhere among the other messages in the syslog:

Dec 19 00:33:20 kirbpad SpringBoard[52026] <Notice>: MS:Notice: Loading: /Library/MobileSubstrate/DynamicLibraries/TestTweak.dylib
Dec 19 00:33:20 kirbpad SpringBoard[52026] <Warning>: It works!

Have fun - and don’t forget to thank coolstar for being awesome.

Running Substrate tweaks in the iOS Simulator

Posted on Oct 21, 2013 by FTO and kirb — Comments

With the iPhone 4 being the last iOS 7 device standing that can be jailbroken tethered, it’s gotten much harder to test and update tweaks for the new firmware ahead of the untethered jailbreak release.

But there’s still a way to do this if you’re on a Mac. The iOS Simulator is basically already jailbroken, in the sense that you can access its filesystem and not all security policies are enforced. Therefore you can test your SpringBoard tweaks on iOS 7 with the simulator.

Do keep in mind that this will only work with tweaks that only need to load into SpringBoard or other daemons. If you want to test a tweak inside apps, unfortunately you’ll need to wait for a proper Substrate for OS X release that can do this.

Installing

First, you need to install Substrate. The binaries in the Substrate package in Cydia (except for MobileSafety) are compiled for both armv6 and i386/x86_64, so you can simply download, extract, and copy the files out of the package like so:

wget http://apt.saurik.com/debs/mobilesubstrate_0.9.4001_iphoneos-arm.deb
mkdir substrate
dpkg-deb -x mobilesubstrate_0.9.4001_iphoneos-arm.deb substrate
sudo mv substrate/Library/Frameworks/CydiaSubstrate.framework /Library/Frameworks/CydiaSubstrate.framework
sudo mv substrate/Library/MobileSubstrate /Library/MobileSubstrate
sudo mv substrate/usr/lib/* /usr/lib

Injecting

Awesome, now we need to get it to inject into the iOS Simulator. As of iOS 7, SpringBoard is launched by launchd_sim instead of directly by the simulator binary, so the hack that many people used in the past no longer works. However, you can still use Substrate by manually modifying LaunchDaemon plists found in the SDK.

Head over to the LaunchDaemons directory of the simulator’s sysroot on your Mac:

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/System/Library/LaunchDaemons

Make a backup of com.apple.SpringBoard.plist somewhere other than the LaunchDaemons directory (otherwise SpringBoard will be loaded twice).

Now open the original plist in your favorite text or plist editor (Xcode has one built in). It’s a binary plist, so if your text editor isn’t cool enough to convert it to XML automatically, you can do so with plutil -convert xml1 com.apple.SpringBoard.plist. Add an EnvironmentVariables key, and set its value to a dictionary containing DYLD_INSERT_LIBRARIES, and set the value of that to the location of your dylib (not MobileSubstrate.dylib).

You should end up with something like this:

Modified com.apple.SpringBoard.plist

If you have the simulator running, close and re-launch it for the changes to apply.

Environment

Next, you must export $IPHONE_SIMULATOR_ROOT. Open ~/.bash_profile (or the equivalent for your shell) and add:

export IPHONE_SIMULATOR_ROOT=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk

Load the changes into your shell by executing . ~/.bash_profile, or just close and launch a new shell.

Finally, you need to make a minor modification to theos, since the iOS 7 SDK’s ld doesn’t like one of the flags that theos passes to it. Open $THEOS/makefiles/targets/Darwin/simulator.mk in your editor, and find the following line:

_TARGET_OSX_VERSION_FLAG = -mmacosx-version-min=$(if $(_TARGET_VERSION_GE_4_0),10.6,10.5)

Replace it with this:

_TARGET_VERSION_GE_7_0 = $(call __simplify,_TARGET_VERSION_GE_7_0,$(shell $(THEOS_BIN_PATH)/vercmp.pl $(_THEOS_TARGET_SDK_VERSION) ge 7.0))
_TARGET_OSX_VERSION_FLAG = $(if $(_TARGET_VERSION_GE_7_0),-miphoneos-version-min=7.0,-mmacosx-version-min=$(if $(_TARGET_VERSION_GE_4_0),10.6,10.5))

Linking

Your copy of libsubstrate.dylib won’t be able to be linked against, since it doesn’t have a slice for the iOS Simulator platform. You can download a version that does like so:

mv $THEOS/lib/libsubstrate.dylib $THEOS/lib/libsubstrate.dylib_
wget http://cdn.hbang.ws/dl/libsubstrate_ios7sim.dylib -O $THEOS/lib/libsubstrate.dylib

Note that this only contains an x86_64 slice for OS X, since it isn’t possible to have multiple i386 slices. However, you won’t need to worry about this unless you have OS X projects that depend on OS X 10.5 or older.

Compiling

Simply compiling tweaks like normal is not enough for them to work in the simulator: in this case, they’ll be compiled for the ARM architecture, and your Mac runs on Intel. It’s fairly simple to do this with theos, however, by setting the TARGET variable.

If you don’t already have TARGET set in your makefile, at the top of your makefile, add this line:

TARGET = simulator

If you do, set the target parameter to simulator. For example:

TARGET = simulator:clang:7.0

(Check out theiostream’s documentation on TARGET if you haven’t tried it before.)

That’s it!

You can now run make, ignoring the warning that ld whines about. SpringBoard will not be restarted for you, like usual – you must execute killall SpringBoard yourself.