📜 ⬆️ ⬇️

theos: writing a tweak for iOS SpringBoard

Good skoronovogodnogo evening dear habraludyam!

Today I will talk about creating a tweak for iOS SpringBoard using theos . What for? As an interesting story and workout. At the end of the tutorial we will get something like this right on the screen of the blocking of our i-device:



Creating a project and setting up theos


We start: we create an empty folder, we throw Teos into it (I threw it in the form of a guitar submodule).
')
Next, create a new project using the NIC:

iHabrTweak git:(master) theos/bin/nic.pl NIC 2.0 - New Instance Creator ------------------------------ [1.] iphone/application [2.] iphone/library [3.] iphone/preference_bundle [4.] iphone/tool [5.] iphone/tweak Choose a Template (required): 5 Project Name (required): iHabrTweak Package Name [com.yourcompany.ihabrtweak]: com.silvansky.ihabr Author/Maintainer Name [Valentine Silvansky]: silvansky [iphone/tweak] MobileSubstrate Bundle filter [com.apple.springboard]: Instantiating iphone/tweak in ihabrtweak/... Done. 

Now we have a folder ihabrtweak, in which we need the files we need.

 iHabrTweak git:(master) ✗ cd ihabrtweak ihabrtweak git:(master) ✗ ls Makefile Tweak.xm control iHabrTweak.plist theos 

Now run make and see errors: not everything is so simple! Our system is not fully ready for theos testing.

Well, it is necessary to enter the settings necessary for normal assembly:

 export ARCHS=armv7 export TARGET=iphone:latest:4.3 export THEOS="`pwd`/theos" export SDKVERSION=6.0 export THEOS_DEVICE_IP=192.168.2.2 

ARCHS indicates to us that we will only collect for armv7, and on armv6 we will score. TARGET tells us what we will build for iOS using the latest (in the system) SDK and with compatibility from version 4.3. The remaining three are self-evident.

 ihabrtweak git:(master) ✗ make Making all for tweak iHabrTweak... Preprocessing Tweak.xm... Compiling Tweak.xm... Linking tweak iHabrTweak... Stripping iHabrTweak... Signing iHabrTweak... ihabrtweak git:(master) ✗ ls .theos/obj Tweak.xm.o iHabrTweak.dylib 

Now we have our wonderful dynamic library, which so far is not able to do anything at all! But we can install our tweak on the device:

 ihabrtweak git:(master) ✗ make package Making all for tweak iHabrTweak... make[2]: Nothing to be done for `internal-library-compile'. Making stage for tweak iHabrTweak... dpkg-deb: building package `com.silvansky.ihabr' in `./com.silvansky.ihabr_0.0.1-1_iphoneos-arm.deb'. ihabrtweak git:(master) ✗ make package install Making all for tweak iHabrTweak... make[2]: Nothing to be done for `internal-library-compile'. Making stage for tweak iHabrTweak... dpkg-deb: building package `com.silvansky.ihabr' in `./com.silvansky.ihabr_0.0.1-2_iphoneos-arm.deb'. install.copyFile "./com.silvansky.ihabr_0.0.1-2_iphoneos-arm.deb" "com.silvansky.ihabr_0.0.1-2_iphoneos-arm.deb" root@192.168.2.2's password: com.silvansky.ihabr_0.0.1-2_iphoneos-arm.deb 100% 1454 1.4KB/s 00:00 install.exec "dpkg -i com.silvansky.ihabr_0.0.1-2_iphoneos-arm.deb" root@192.168.2.2's password: Selecting previously deselected package com.silvansky.ihabr. (Reading database ... 2516 files and directories currently installed.) Unpacking com.silvansky.ihabr (from com.silvansky.ihabr_0.0.1-2_iphoneos-arm.deb) ... Setting up com.silvansky.ihabr (0.0.1-2) ... install.exec "timeout 10s sbreload || ( ( respring || killall -9 SpringBoard ) && launchctl load /System/Library/LaunchDaemons/com.apple.SpringBoard.plist )" root@192.168.2.2's password: launchctl unload SpringBoard.plist waiting for kill(29) != 0... 

Actually, the tweak is ready! Put, but does nothing. We will rule it. Let's start with the theory of theos and its tweaks.
As you have already noticed, in the project we have the file Tweak.xm, which is our main source.

At the moment everything is commented out in it, and the commentary itself is partial documentation. Actually, this file is a template for generating the final .mm file. Consider some useful macros for this template:

% hook and% end


The base tweaks in theos are hooks. They are based on the richest runtime of the Objective-C language, which allows the substitution of methods for an arbitrary class. Actually, it is used like this:

 %hook SomeClass -(void)someMethod { // some code goes here } %end 

Here We are introducing (replacing) the method "someMethod" in the class "SomeClass". For example, we can embed our code in the SpringBoard, for example, we can add our views to the lock screen.

% orig and% new


Well, we have redefined the method, but how to call the original one? Yes is also very simple! For this there is a macro% orig. Being called without parameters, this macro redirects to the original function the same parameters as it came to our hook. But you can also transfer any of your own:

 %hook SomeClass - (id)initWithFrame:(CGRect)frame { id result = %orig; // some custom code return result; } - (id)initWithName:(NSString *)name { id result = %orig(@"customName"); // some custom code return result; } %end 

If simple method definitions inside the hooks override existing ones, then the macro% new can be used to add new methods. In essence, this is a separator between the methods we replace and the methods we add. ALL methods coming after% new will be added. Example:

 %hook SomeClass - (void)someOldMethod { // some code here } %new - (void)someNewMethod { // some more code here } %end 

But with this approach, we will not be able to call our new method from the redefined one: theos treats vornings as errors and will not allow to build a project. After all, we have not announced our method! But this is fixable, just add this to our file:

 @interface SomeClass(NewMethods) - (void)someNewMethod; @end; 

% log


The% log macro allows you to write a function call to the system log. Usually used for debugging.

Other macros can be viewed here .

We write something useful


Complete with theos, we get the system framework headers. In our project, they are in theos/include . If they do not lie, do not forget to do this:

 cd theos git submodule init git submodule update 

There we find the folder SpringBoard, and in it - a bunch of headers. Well, let's go through the class names. Note the interesting class SBAwayView, which is the main view of the lock screen. Well, we will put hooks in it. First you need to catch the moment of its creation:

 #import <SpringBoard/SBAwayView.h> #import <UIKit/UIKit.h> %hook SBAwayView -(id)initWithFrame:(CGRect)frame { id result = %orig; if (result) { // here goes the code... } return result; } %end 

We can put% log and make sure after the build-installation that this method is actually called. Now we can add new views! Just where? Let's add them to the background image. We find ivar UIImageView *_backgroundView for the SBSlidingAlertDisplay class, from which SBAwayView is inherited, we also find the method -(CGRect)middleFrame; . But how do we get the ivar value? Let's google Find the MSHookIvar function that will do everything:

 #import <SpringBoard/SBAwayView.h> #import <UIKit/UIKit.h> #import <substrate.h> %hook SBAwayView -(id)initWithFrame:(CGRect)frame { id result = %orig; if (result) { CGRect labelRect = [self middleFrame]; labelRect.origin.y = labelRect.origin.y + 20.f; labelRect.size.height = 50.f; UILabel *habrLabel = [[[UILabel alloc] initWithFrame:labelRect] autorelease]; habrLabel.text = @"Hello, Habr!"; habrLabel.textColor = [UIColor colorWithRed:155.f/255.f green:182.f/255.f blue:206.f/255.f alpha:1.f]; habrLabel.opaque = NO; habrLabel.textAlignment = UITextAlignmentCenter; habrLabel.font = [UIFont boldSystemFontOfSize:36]; habrLabel.backgroundColor = [UIColor clearColor]; UIImageView *backgroundView = MSHookIvar<UIImageView *>(self, "_backgroundView"); [backgroundView addSubview:habrLabel]; } return result; } %end 

Run and enjoy the spectacle!

Now we will complicate the task. We will upload a picture! In theory, everything is simple: instead of UILabel, create a UIImageView. And where does the picture come from?

The picture should be put in the SpringBoard.app bundle, but it is better if the picture itself is copied during the package installation. To do this, we will reorganize the project structure: create the Layout folder, in it the DEBIAN folder, where we will move the existing control file, next to the DEBIAN folder, make System / Library / CoreServices / SpringBoard.app, where we place our picture:

 SpringBoard.app git:(master) pwd /Users/silvansky/Projects/iHabrTweak/ihabrtweak/Layout/System/Library/CoreServices/SpringBoard.app SpringBoard.app git:(master) ls habr_logo_hat.png 


Now you can write the final New Year code:

 #import <SpringBoard/SBAwayView.h> #import <UIKit/UIKit.h> #import <substrate.h> #define IMG_WIDTH 150.f #define IMG_HEIGHT 186.f %hook SBAwayView -(id)initWithFrame:(CGRect)frame { id result = %orig; if (result) { CGRect imageRect = [self middleFrame]; imageRect.origin.y = imageRect.origin.y + 20.f; imageRect.origin.x = (imageRect.size.width - IMG_WIDTH) / 2.f; imageRect.size.width = IMG_WIDTH; imageRect.size.height = IMG_HEIGHT; UIImageView *habrLogoView = [[[UIImageView alloc] initWithFrame:imageRect] autorelease]; habrLogoView.image = [UIImage imageNamed:@"habr_logo_hat"]; UIImageView *backgroundView = MSHookIvar<UIImageView *>(self, "_backgroundView"); [backgroundView addSubview:habrLogoView]; } return result; } %end 

And - admiring the resulting beauty:


Full source, as usual, please take a githaba .

Happy New Year! Joy and good luck next year! =)

Source: https://habr.com/ru/post/164341/


All Articles