...

четверг, 23 января 2014 г.

[recovery mode] Создание твика на примере приложения Телефон. Да будет плюс!

Здравствуйте, хабражители!

Предыстория




После выхода iOS 7 некоторые пользователи начали жаловаться на проблемы с приложением Телефон. Суть проблемы в том, что при наборе номера в международном формате +375 (код) xxx-xx-xx не удается набрать '+'. Если удерживать '0', то вместо плюса получаем комбинацию из трех пальцев '0+'. Проблема скорее всего локальна, так как кроме пользователей из Беларуси больше никто свое недовольство не высказывал.

По разным причинам я долго не обновлялся до iOS 7. Но обновившись был неприятно удивлен. Проблема осталась, несмотря на выход нескольких минорных обновлений. Почитав форумы, нашел следующие варианты решения этой проблемы:



  1. использовать 8 при наборе номера

  2. использовать 00

  3. удерживая '0', нажать кнопку удалить до появления плюса




Первый вариант вполне себе работоспособный. С помощью второго звонок сделать не получилось, скорее всего мой оператор не поддерживает такой формат. Третий работает, но для этого нужна сноровка и вторая рука, а на улице холодно :)

И тогда я решил исправить это маленькое недоразумение с помощью твика.



Поехали!




На хабре уже была статья про создание твика с помощью theos, поэтому создание проекта и настройку theos пропустим и перейдем сразу к интересному.

Итак, раз мы собрались что-то менять в приложении Телефон, хорошо бы узнать что оно из себя представляет внутри. У меня на устройстве приложение лежало в /var/stash/Applications.BTFCTa/MobilePhone.app. Воспользуемся утилитой class-dump и получим список прототипов классов.



➜ class-dump -H -o MobilePhone MobilePhone.app




Как оказалось их не так уж и мало.

Список хедеров

ABNewPersonViewControllerDelegate-Protocol.h

ABPeoplePickerNavigationControllerDelegate-Protocol.h

ABPeoplePickerNavigationControllerPrivateMemberCellDelegate-Protocol.h

ABUnknownPersonViewControllerDelegate-Protocol.h

AVCaptureFileOutputRecordingDelegate-Protocol.h

AudioDeviceController.h

CDStructures.h

CNFRegWizardControllerDelegate-Protocol.h

CommunicationDisplayViewController.h

ConferenceManagementTable.h

DialerController.h

DialerLCDFieldDelegate-Protocol.h

DialerLCDFieldProtocol-Protocol.h

DialerViewDelegate-Protocol.h

IDSIDQueryControllerDelegate-Protocol.h

InCallBottomButton.h

InCallController.h

InCallLCDField.h

InCallLCDView.h

MPDetailSliderDelegate-Protocol.h

MobilePhoneApplication.h

NSArray-MPRecentsExtensions.h

NSDate-DayComparison.h

NSDictionary-PHVoicemailAudioController.h

NSDictionary-VoicemailAudioRouting.h

NSError-VoicemailExtras.h

NSIndexSet-MPRecentsExtensions.h

NSObject-Protocol.h

PHAbstractDialerView.h

PHAddressBookController.h

PHAudioPlayer.h

PHAudioPlayerDataSource-Protocol.h

PHAudioPlayerDelegate-Protocol.h

PHAudioPlayerVoicemailDataSource.h

PHAudioRecorder.h

PHAudioRecorderDelegate-Protocol.h

PHConferenceParticipantCell.h

PHConferenceParticipantCellProtocol-Protocol.h

PHEmergencyDialerButton.h

PHEmergencyDialerViewController.h

PHEmergencyHandsetDialerLCDView.h

PHEmergencyHandsetDialerView.h

PHFavoritesCell.h

PHFavoritesContactPhotoView.h

PHFavoritesEntry.h

PHFavoritesManager.h

PHFavoritesViewController.h

PHHandsetDialerLCDView.h

PHHandsetDialerNameLabelView.h

PHHandsetDialerView.h

PHInCallNumberPadButton.h

PHInCallRingView.h

PHInfoButtonMaskView.h

PHRecentCall.h

PHRecentMultiCall.h

PHRecentsCell.h

PHRecentsManager.h

PHRecentsPersonFaceTimeHeaderSummaryView.h

PHRecentsPersonFaceTimeHeaderView.h

PHRecentsPersonHeaderSummaryView-Protocol.h

PHRecentsPersonHeaderView.h

PHRecentsPersonPhoneHeaderSummaryView.h

PHRecentsPersonPhoneHeaderView.h

PHRecentsToggleButton.h

PHRecentsViewController.h

PHStarkActionSheetTableViewCell.h

PHStarkActionSheetViewController.h

PHStarkDialerLCDView.h

PHStarkDialerView.h

PHStarkDialerViewController.h

PHStarkFavoritesTableViewCell.h

PHStarkFavoritesViewController.h

PHStarkGenericTableViewCell.h

PHStarkGenericTableViewController.h

PHStarkGenericViewController.h

PHStarkHardwareControlsBroadcaster.h

PHStarkHardwareMenuTableViewCell.h

PHStarkInCallDialerLCDView.h

PHStarkInCallDialerView.h

PHStarkInCallKeypadViewController.h

PHStarkInCallViewController.h

PHStarkLozengeLabel.h

PHStarkMainMenuContainerViewController.h

PHStarkManager.h

PHStarkNoContentBannerView.h

PHStarkPlayPauseButton.h

PHStarkRecentsTableViewCell.h

PHStarkRecentsViewController.h

PHStarkRootContainerViewController.h

PHStarkTelephonyStateMonitor.h

PHStarkTelephonyStateMonitorDelegate-Protocol.h

PHStarkVoicemailManager.h

PHStarkVoicemailPlayerViewController.h

PHStarkVoicemailTableViewCell.h

PHStarkVoicemailViewController.h

PHStaticDialerPad.h

PHTextCycleLabel.h

PHVoicemailAudioController.h

PHVoicemailBlockedListViewController.h

PHVoicemailCell.h

PHVoicemailCellConfigurationDelegate-Protocol.h

PHVoicemailCellDelegate-Protocol.h

PHVoicemailFolderCell.h

PHVoicemailGreetingCell.h

PHVoicemailGreetingViewController.h

PHVoicemailGreetingViewControllerDelegate-Protocol.h

PHVoicemailInboxListViewController.h

PHVoicemailListMaskView.h

PHVoicemailListMaskViewDelegate-Protocol.h

PHVoicemailListViewController.h

PHVoicemailListViewControllerConcrete-Protocol.h

PHVoicemailNavigationController.h

PHVoicemailNoContentViewController.h

PHVoicemailSetupViewController.h

PHVoicemailSlider.h

PHVoicemailTrashListViewController.h

PHVoicemailUnavailableCell.h

PhoneApplication.h

PhoneBadgeable-Protocol.h

PhoneBaseViewController-Protocol.h

PhoneContentView.h

PhoneDesktopView.h

PhoneNavigationController.h

PhoneRootView.h

PhoneRootViewController.h

PhoneTabBarController.h

PhoneTabViewController-Protocol.h

PhoneViewController.h

RadiosPreferencesDelegate-Protocol.h

SixSquareButton.h

SixSquareView.h

TPDialerKeypadDelegate-Protocol.h

TPSetPINViewControllerDelegate-Protocol.h

TPStarkInCallViewControllerDelegate-Protocol.h

TPSuperBottomBarDelegateProtocol-Protocol.h

UIActionSheetDelegate-Protocol.h

UIApplicationDelegate-Protocol.h

UIFont-MobilePhoneAdditions.h

UIFont-UIFont_InCallLCDView.h

UIGestureRecognizerDelegate-Protocol.h

UIImage-MobilePhoneAdditions.h

UINavigationControllerDelegate-Protocol.h

UIScrollViewDelegate-Protocol.h

UITabBarControllerDelegate-Protocol.h

UITableView-PHStarkExtensions.h

UITableViewCell-VoicemailCellAdditions.h

UITableViewDataSource-Protocol.h

UITableViewDelegate-Protocol.h

UIViewController-Testing.h

VMVoicemail-MobilePhone.h

VideoConferenceController.h





Дальнейшая логика моих действий была следующей. Раз у нас проблема при нажатии на кнопку, значит нужно искать что-то, что связано с ней.

➜ ls | grep -i "key"
PHStarkInCallKeypadViewController.h
TPDialerKeypadDelegate-Protocol.h




TPDialerKeypadDelegate-Protocol.h оказался довольно интересным. В нем, как видно, описаны методы, отвечающие за нажатие кнопок.

#import "NSObject.h"

@protocol TPDialerKeypadDelegate <NSObject>

@optional
- (void)phonePad:(id)arg1 dialerCharacterButtonWasHeld:(unsigned int)arg2;
- (void)phonePad:(id)arg1 replaceLastDigitWithString:(id)arg2;
- (void)phonePadDidEndSounds:(id)arg1;
- (void)phonePadWillBeginSounds:(id)arg1;
- (void)phonePad:(id)arg1 keyUp:(BOOL)arg2;
- (void)phonePad:(id)arg1 keyDown:(BOOL)arg2;
- (void)phonePadDeleteLastDigit:(id)arg1;
- (void)phonePad:(id)arg1 appendString:(id)arg2;
@end




Что ж, раз это протокол, значит кто-то его реализует!

➜ grep -l -r "TPDialerKeypadDelegate" .
./DialerController.h
./InCallController.h
./PHEmergencyDialerViewController.h
./PHHandsetDialerView.h
./TPDialerKeypadDelegate-Protocol.h




В дальнейшем методом научного тыка было выявлено, что DialerController — нужный нам класс, а phonePad:replaceLastDigitWithString: — нужный нам метод. В этом можно убедиться написав следующий код:

#import <substrate.h>

%hook DialerController

- (void)phonePad:(id)arg1 replaceLastDigitWithString:(id)arg2 {
%log;
%orig(arg1, arg2);
}

%end




В логе видно, что последний параметр это то, что нам надо:

<Warning>: -[<DialerController: 0x165e91e0> phonePad:<TPDialerNumberPad: 0x1677fc40; baseClass = UIControl; frame = (28 84; 264 296); opaque = NO; layer = <CALayer: 0x1677f1d0>> replaceLastDigitWithString:+]




Хорошо бы теперь найти где хранится сама строка, к которой и добавляется наш плюс. Для этого снова заглянем в DialerController.

DialerController.h


#import "PhoneViewController.h"

#import "ABNewPersonViewControllerDelegate.h"
#import "ABPeoplePickerNavigationControllerDelegate.h"
#import "DialerViewDelegate.h"
#import "TPDialerKeypadDelegate.h"
#import "UIActionSheetDelegate.h"

@class NSString, NSTimer, PHAbstractDialerView, UINavigationController;

@interface DialerController : PhoneViewController <ABNewPersonViewControllerDelegate, ABPeoplePickerNavigationControllerDelegate, DialerViewDelegate, UIActionSheetDelegate, TPDialerKeypadDelegate>
{
PHAbstractDialerView *_dialerView;
UINavigationController *_newContactNavigationController;
NSTimer *_deleteTimer;
NSTimer *_lookupTimer;
NSString *_lastDialedNumberCache;
NSString *_myPrefix;
int _shouldUseMyPrefixAsHint;
unsigned int _calledNumber:1;
unsigned int _didDeleteRepeat:1;
unsigned int _dtmfPlaying;
int _dialerType;
}

+ (id)defaultPNGName;
+ (id)tabBarIconName;
+ (id)tabBarIconImageSelected;
+ (id)tabBarIconImage;
+ (id)tabBarIconImageName;
+ (int)tabViewType;
+ (BOOL)launchFieldTestIfNeeded:(id)arg1;
+ (BOOL)shouldStringAutoDial:(id)arg1 givenLastChar:(BOOL)arg2;
@property int dialerType; // @synthesize dialerType=_dialerType;
@property(readonly) PHAbstractDialerView *dialerView; // @synthesize dialerView=_dialerView;
- (void)_statusBarHeightChanged:(id)arg1;
- (void)_handleSIMInsertionOrRemoval;
- (void)performDeleteAction;
- (void)performCallAction;
- (void)_deleteButtonDown:(id)arg1;
- (void)_deleteButtonClicked:(id)arg1;
- (void)_stopDeleteTimer;
- (void)_startDeleteTimer;
- (void)_deleteRepeat;
- (void)peoplePickerNavigationController:(id)arg1 insertEditorDidConfirm:(BOOL)arg2 forPerson:(void *)arg3;
- (BOOL)peoplePickerNavigationController:(id)arg1 shouldShowInsertEditorForPerson:(void *)arg2 insertProperty:(int *)arg3 copyInsertValue:(id *)arg4 copyInsertLabel:(id *)arg5;
- (BOOL)peoplePickerNavigationController:(id)arg1 shouldContinueAfterSelectingPerson:(void *)arg2 property:(int)arg3 identifier:(int)arg4;
- (BOOL)peoplePickerNavigationController:(id)arg1 shouldContinueAfterSelectingPerson:(void *)arg2;
- (void)peoplePickerNavigationControllerDidCancel:(id)arg1;
- (void)newPersonViewController:(id)arg1 didCompleteWithNewPerson:(void *)arg2;
- (void)actionSheet:(id)arg1 clickedButtonAtIndex:(int)arg2;
- (void)_dismissNewContactView:(BOOL)arg1;
- (void)actionSheet:(id)arg1 didDismissWithButtonIndex:(int)arg2;
- (void)_addButtonClicked:(id)arg1;
- (void)_addToExistingContact;
- (void)_addToNewContact;
- (id)_qualifyNumberIfNecessary:(id)arg1;
- (void *)_newPersonWithValue:(id)arg1 forMultiValueProperty:(int)arg2;
- (void)_hideNewContactView;
- (void)_showNewContactView;
- (void)_dialVoicemail;
- (void)phonePad:(id)arg1 keyUp:(BOOL)arg2;
- (void)phonePad:(id)arg1 keyDown:(BOOL)arg2;
- (void)phonePadDidEndSounds:(id)arg1;
- (id)_myPrefix;
- (BOOL)_shouldUseMyPrefixAsHint;
- (void)phonePadDeleteLastDigit:(id)arg1;
- (void)phonePad:(id)arg1 replaceLastDigitWithString:(id)arg2;
- (void)_phonePad:(id)arg1 appendString:(id)arg2 suppressClearingDialedNumber:(BOOL)arg3;
- (void)phonePad:(id)arg1 appendString:(id)arg2;
- (void)phonePad:(id)arg1 dialerCharacterButtonWasHeld:(unsigned int)arg2;
- (void)starkInCallViewControllerAppearedNotification:(id)arg1;
- (void)_callButtonPressed:(id)arg1;
- (void)_callButtonLongPress;
- (void)_updateCallButtonEnabledState:(id)arg1;
- (void)_updateLCDNameLabelWithOriginallyPastedString:(id)arg1;
- (void)_updateLCDNameLabelWithAMatchingName:(BOOL)arg1;
- (void)_updateCallButtonEnabledState:(id)arg1 updateNameNow:(BOOL)arg2;
- (void)dialerView:(id)arg1 stringWasPasted:(id)arg2;
- (void)dialerViewTextDidChange:(id)arg1;
@property(retain) NSString *lastDialedNumber;
- (void)_getPersonName:(id *)arg1 personLabel:(id *)arg2 personUID:(int *)arg3 forPhoneNumberString:(id)arg4;
- (void)_updateName;
- (void)_stopLookupTimer;
- (BOOL)shouldSnapshot;
- (void)prepareForSnapshot;
- (void)_clearDisplayIfNecessary;
- (void)dealloc;
- (id)initWithDialerType:(int)arg1;
- (void)applicationDidResume;
- (void)viewDidDisappear:(BOOL)arg1;
- (void)viewWillDisappear:(BOOL)arg1;
- (void)viewDidAppear:(BOOL)arg1;
- (void)viewWillAppear:(BOOL)arg1;
- (BOOL)_isFirstLaunchFromDefaultPNGToDialer;
- (BOOL)isShowingDoubleHeightStatusBar;
- (void)unloadView;
- (void)didReceiveMemoryWarning;
- (void)dialerViewPhoneNumberWasTapped:(id)arg1;
- (void)loadView;

@end







Печаль, явного места, где бы хранился текст, не видно. Но посмотрим на переменную _dialerView и класс PHAbstractDialerView.

PHAbstractDialerView.h


#import "UIView.h"

#import "DialerLCDFieldDelegate.h"

@class UIControl, UIView<DialerLCDFieldProtocol>, UIView<TPDialerKeypadProtocol>;

@interface PHAbstractDialerView : UIView <DialerLCDFieldDelegate>
{
BOOL _inCallMode;
UIView<DialerLCDFieldProtocol> *_lcdView;
UIView<TPDialerKeypadProtocol> *_phonePadView;
id <DialerViewDelegate> _delegate;
UIControl *_addContactButton;
UIControl *_callButton;
UIControl *_deleteButton;
}

@property(retain, nonatomic) UIControl *deleteButton; // @synthesize deleteButton=_deleteButton;
@property(retain, nonatomic) UIControl *callButton; // @synthesize callButton=_callButton;
@property(retain, nonatomic) UIControl *addContactButton; // @synthesize addContactButton=_addContactButton;
@property(nonatomic) id <DialerViewDelegate> delegate; // @synthesize delegate=_delegate;
@property(retain, nonatomic) UIView<TPDialerKeypadProtocol> *phonePadView; // @synthesize phonePadView=_phonePadView;
@property(retain, nonatomic) UIView<DialerLCDFieldProtocol> *lcdView; // @synthesize lcdView=_lcdView;
@property(nonatomic) BOOL inCallMode; // @synthesize inCallMode=_inCallMode;
- (void)dialerField:(id)arg1 stringWasPasted:(id)arg2;
- (void)dialerLCDFieldTextDidChange:(id)arg1;
- (void)dealloc;

@end







В нем есть вьюшка UIView<DialerLCDFieldProtocol> *_lcdView, которая поддерживает протокол DialerLCDFieldProtocol.

DialerLCDFieldProtocol-Protocol.h


#import "NSObject.h"

@protocol DialerLCDFieldProtocol <NSObject>
- (void)setDelegate:(id)arg1;
- (void)setHighlighted:(BOOL)arg1;
- (BOOL)highlighted;
- (void)setInCallMode:(BOOL)arg1;
- (BOOL)inCallMode;
- (void)deleteCharacter;
- (void)setText:(id)arg1 needsFormat:(BOOL)arg2;
- (id)text;

@optional
- (void)setText:(id)arg1 needsFormat:(BOOL)arg2 name:(id)arg3 label:(id)arg4;
- (void)setName:(id)arg1 numberLabel:(id)arg2;
@end







Методы setText:needsFormat: и text выглядят многообещающими. Самое время проверить нашу догадку!

#import <substrate.h>

%hook DialerController

- (void)phonePad:(id)arg1 replaceLastDigitWithString:(id)arg2 {
id dialerView = MSHookIvar<id>(self, "_dialerView");

id lcdView = MSHookIvar<id>(dialerView, "_lcdView");

NSString *currentText;
currentText = objc_msgSend(lcdView, @selector(text));
NSLog(@"text: %@", currentText);

objc_msgSend(lcdView, @selector(setText:needsFormat:), @"+375123456789", YES);
}

%end




Жмем '0' на клавиатуре и через некоторое время в логе видим текст с экрана, а еще через мгновение на экране видим следующий результат:

<Warning>: text: (0 )




Картинка





Ну что же, дело осталось за малым. Пишем финальную версию твика.

#import <substrate.h>

%hook DialerController

- (void)phonePad:(id)arg1 replaceLastDigitWithString:(id)arg2 {
id dialerView = MSHookIvar<id>(self, "_dialerView");

id lcdView = MSHookIvar<id>(dialerView, "_lcdView");

NSString *currentText;
currentText = objc_msgSend(lcdView, @selector(text));

currentText = [currentText stringByReplacingOccurrencesOfString:@"(" withString:@""];
currentText = [currentText stringByReplacingOccurrencesOfString:@")" withString:@""];
currentText = [currentText stringByReplacingOccurrencesOfString:@"-" withString:@""];
currentText = [currentText stringByReplacingOccurrencesOfString:@" " withString:@""];

if ([arg2 isEqualToString:@"+"] && currentText.length && [currentText characterAtIndex:currentText.length - 1] == '0') {
currentText = [currentText stringByReplacingCharactersInRange:NSMakeRange(currentText.length - 1, 1) withString:@"+"];
objc_msgSend(lcdView, @selector(setText:needsFormat:), currentText, YES);
}
else {
%orig(arg1, arg2);
}
}

%end




Надеюсь код в пояснении не нуждается, замечу лишь, что значение с экрана приходит отформатированным, поэтому из строки пришлось убрать символы ()- .

Заключение




Код можно найти на github.

Твик можно установить через сидию, предварительно добавив репозиторий http://ift.tt/KKCaEy. Название твика Plus4Belarus.


Работоспособность протестирована на iPhone 4 и iPhone 5 c версией прошивки 7.0.4.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий