Friday, February 27, 2009

0x51EB851F wha?

I am decompiling KBWordSearch for .cin support, but I encountered a strange piece of ASM:

+00174 30b9536c 34329FE5 loc_000174: ldr r3,[pc,#0x234]
+00178 30b95370 C7208BE2 add r2,fp,#0xc7
+0017c 30b95374 9302C1E0 smull r0,r1,r3,r2
+00180 30b95378 C23FA0E1 mov r3,r2,asr #31
+00184 30b9537c 41B363E0 rsb fp,r3,r1,asr #6
+00188 30b95380 30109DE5
+0018c 30b95384 8B32A0E1 mov r3,fp,lsl #5
+00190 30b95388 8B3183E0 add r3,r3,fp,lsl #3
+00194 30b9538c 033183E0 add r3,r3,r3,lsl #2
+00198 30b95390 02B063E0 rsb fp,r3,r2

which translates to:

// in: index
// out: next_index
capacity = 200;
r2 = index + capacity - 1;
r1 = r2 * 0x51EB851F; // take upper dword only.
fp = (r1 >> 6) - (r2 >> 31);
next_index = r2 - fp*160;

(0x51EB851F is the lower dword of 3.14 in IEEE double, and also 237/100+1) Can someone explain what this code is doing?

Edit: Turns out to be a division. Ref:

r2 = index + capacity - 1;
next_index = r2 - (r2/capacity)*160;

Why are they doing things like this is beyond me.

iKeyEx & 5-Row QWERTY 0.1-7 Released

I was not updating last few days because I wanna clear all the homework first. Anyway, there's no major update about ⌘, but there is a huge addition in 5-Row QWERTY.

The front end to modify Info.plist & layout.plist of 5-Row QWERTY has been implemented in v0.1-7, so that you can:
  • Change the auto-correction language, or even use the built-in IMEs:
    (5-Row QWERTY × Traditional Chinese Pinyin IME)
  • Change the default layout to QWERTZ, QZERTY, AZERTY and many others
  • Totally customize the special characters, e.g. change the ^ to ?, $ to €, etc.
There are a few bug fixes and improvements as well, which are recorded in the ChangeLog as usual.

iKeyEx 0.1-7 is a minor update which addresses a few bugs. However, the critical one still has not been fixed because no one has ever given me a crash log or a useful crash log.

Although the layout engine of 5-Row QWERTY only depends on 0.1-6, the preferences binary depends on a single 0.1-7 function "iKeyEx_KBMan" for clearing the cache quickly. Therefore you still need to upgrade iKeyEx to make it work.

Sunday, February 22, 2009

⌘ Development Progress Day 3

Nothing technical, so let's see some screenshots:

Saturday, February 21, 2009

Spelling error slipped into production

Open UIKit (2.2), and search for UIResrouceBundleForNIBBeingDecodedWithCoder. Lol.

Friday, February 20, 2009

⌘ Development Progress Day 2

Now that UILabel is all set, I continue to hook other texts that are long enough to be "commanded", e.g. the UITableHeaderFooterView. There are also some reluctant UILabel that refuses to be hooked. For these I hooked their superviews (UINavigationBar & UIPickerTable) for extracting the text info.

And here comes the real challenge: hooking the UIWebDocumentView. Yes I knew the Clippy 0.95-5 experiment was failed. But this does not prevent anyone else from finding another way to achieve the same goal right?

My first naïve approach is to get the DOM node at the touch point, like this:

[[self webView] elementAtPoint:(touchPoint)];

and like Clippy 0.95-5, it crashes immediately at the 2nd touch. If I trace the class of the returned object*, I can see the class changes from e.g. DOMHTMLAnchorElement to NSCFType. In fact I encountered this before once when trying to implement setSelection() to <textarea/> and <input/> in ℏClipboard using the DOMRange object. It fails on the 2nd call. I call this the "observer effect". Right after you "observed" any DOM objects, the object will "collapse" and you can't reference it reliably anymore.

A workaround is to -retain it right after the DOM object is accessed. But this introduces memory leak. This is really a last-resort option.

And actually it turns out to be work done too complicated. There is a -[UIWebDocumentView approximateNodeAtViewportLocation:] method in the Interaction category which can easily be overlooked. It returns a DOMNode without any observer effect AFAIK. Except it crashes on 2.2 complaining ASSERTION FAILED: !WebThreadIsEnabled() || WebThreadIsLocked(). Well that's easy enough to solve. Just lock the WebThread() everytime we need to access this method.

So I used:

DOMNode* activeNode = [self approximateNodeAtViewportLocation:&(touchPoint)];

and done! The DOMNode is got and we can do anything we want. Too good to be true. Turns out the DOMNode will only be returned on any objects that have actions, i.e., <a>, <input/>, <textarea/>, <img/>, any thing that has onXXX events, but not static texts. If touchPoint is on a static text, nil will be returned.

But why this method prevents the observer effect? After lots of experiments, finally it turns out that it's the WebThreadLock() and WebThreadUnlock() doing the tricks. In fact, if I do

[[self webView] elementAtPoint:(touchPoint)];

then everything works perfectly! At least in the simulator. (Hey Ryan, if you're reading this, as now I've solved this problem, can you add Safari copying back in Clippy 0.95-6? :D)

So, at the end of Day 2, I can hook to any DOMNodes, besides UILabel, UITableHeaderFooterView, UINavigationBar and UIPickerTable.

Note: -[WebView elementAtPoint:] returns a subclass of NSDictionary. By the returned object I mean the value corresponding to the WebElementDOMNode key.

Thursday, February 19, 2009

⌘ Development Progress Day 1

OK, now iKeyEx 0.1-6 has been released, let's turn into the other project left over: ⌘.

⌘ was proposed on Jan 26th as a solution to issue 10. You can read the design concept here. As I develop the app, I'll keep updating the progress on this blog.

The first thing I've done is to intercept touches coming to a UILabel using -[UILabel touchesBegan:withEvent:]. It turns out not so easy. By default UILabel ignores all touches. But setting userInteractionEnabled = YES does not solve the problem at all (at least in 2.0 firmware). As usual, when something does not work, I look into the UIKit's assembly code. And it turns out that there is a -[UILabel ignoresMouseEvents] method. So I quickly hooked it to always return NO. Then the test application crashed, complaining -[UIScrollView old_touchesBegan:withEvent:] not found. But hey, I did not do anything to the UIScrollView! What's wrong? Turns out the implementation of -[UIResponder touchesBegan:withEvent:] looks like this:

-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
forwardMethod2(self, _cmd, touches, event);

where forwardMethod2() is equivalently the statement [[self nextResponder] performSelector:_cmd withObject:touches withObject:event]. This pattern is used for all touchesXXX:withEvent: messages. Now since I have renamed the old touchesBegan:withEvent: for UILabel only, and my code forwards the message to the next responder (the UIScrollView), and it does not recognize old_touchesBegan:withEvent: -- blam, crash.

As a result, I have to store the old IMP before MobileSubstrate-ing it, and invoke the old message like this:

-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
NSLog(@">>> %@", self.text);
old_touchesBegan_withEvent(self, _cmd, touches, event);

With this, I can hook all UILabels in the simulator.


I've sent the latest versions (r167) of iKeyEx, ℏClipboard, 5 Row QWERTY, L33tTyper and MathTyper to the BigBoss. Hopefully they'll be available tonight.

Wednesday, February 18, 2009

5 Row QWERTY 0.1-6 released

5 Row QWERTY 0.1-6 is released, and iKeyEx is also upgraded to 0.1-6 to support the features of it.

I've put up a few cached images of the new layout at

BTW, if there's no more blocking issues, the new versions of iKeyEx, ℏClipboard, 5 Row QWERTY, MathTyper and L33tTyper should hit BigBoss tomorrow morning (GMT+8).