/* Block 2 */ int pt2[5]; /* An array which starts at r1 + 0x40 */ memset(pt2, 0, 20); /* 20 = 0x14 */
where I've avoided checking what value the memory gets cleared with since it isn't interesting at this moment in time.
Our next block is interesting because it calls one of the API functions we don't know about.
; Block 3 91f43314 addi r4,r1,0x40 91f43318 lwz r3,0x378(r1) 91f4331c addi r0,r2,0x74 91f43320 addi r5,r1,0x370 91f43324 stw r0,0x50(r1) 91f43328 bl 0x91f6708c ; symbol stub for: _CGSNewTransition
Straight away we can see that only r3, r4 and r5 are set implying that this API call takes 3 arguments. The first argument, r3, is loaded from the memory location r1 + 0x378. Referring back to block 1 we see that this is the value returned from _CGSDefaultConnection(). The second argument, r4, is set to point to memory location r1 + 0x40. As we have seen above, this is a 20 byte area of memory previously cleared by memset and then modified by the code between blocks 2 and 3. The final argument, r5, is set to point to memory location r1 + 0x370. Since this isn't really set anywhere in the disassembly we can assume that this is a pointer to some temporary variable. We can now write a C version of this block:
/* Block 3 */ int t3; /* Temp. variable stored at r1 + 0x370 */ CGSNewTransition(t1, pt2, &t3);
We now investigate the last two blocks which contain interesting API calls.
; Block 4 91f433f0 lwz r4,0x370(r1) 91f433f4 cmpwi cr7,r4,__register_frame_info.eh 91f433f8 beq+ cr7,0x91f43404 91f433fc lwz r3,0x378(r1) 91f43400 bl 0x91f66fec ; symbol stub for: _CGSReleaseTransition ... ; Block 5 91f43484 lwz r3,0x378(r1) 91f43488 addis r2,r31,0x3 91f4348c lwz r4,0x370(r1) 91f43490 lfs f1,0xde60(r2) 91f43494 bl 0x91f66fcc ; symbol stub for: _CGSInvokeTransition ... 91f4357c mtcrf 56,r11 91f43580 b restFP ; End of method
By examining the values of r3 and r4 and comparing with block 3 we can easily write equivalent C for these blocks:
/* Block 4 */ CGSReleaseTransition(t1, t3); /* Block 5*/ CGSInvokeTransition(t1, t3, f1);
where f1 is some floating point argument. Comparing it with CGSSetWorkspaceWithTransition() we can probably be fairly sure it is the duration of the transition in seconds. The hard work is now over. We've discovered the number of arguments each API call takes and where their values come from. The remaining task is to combine these arguments with what we already know to give them names more meaningful than t1, etc.
From the call to _CGSDefaultConnection() it is clear that t1 holds a reference to the application's connection to the Window Server. The variable t3 is set by CGSNewTransition() and used by CGSReleaseTransition() and CGSInvokeTransition(). We can therefore suppose that it is some handle which refers to the created transition. The memory at pt2 is passed only to CGSNewTransition() so we can suppose that it is a structure which specifies the various parameters of the transition. The call CGSReleaseTransition(), interestingly, happens before CGSInvokeTransition(). It seems unlikely that both of these functions would be called in that order so we may suppose that the transition is only released if some other operation fails and that invoking the transition implicitly releases it. Using this information we can flesh our our C version:
CGSConnectionRef cid;
int transitionHandle;
int transitionSpec[5];
float duration;
cid = _CGSDefaultConnection();
memset(transitionSpec, 0, 20); /* 20 = 0x14 */
/* ... modifiy contents of transitionSpec (between blocks 2 and 3) ... */
CGSNewTransition(cid, transitionSpec, &transitionHandle);
/* ... find out if we failed ... */
if(failed)
CGSReleaseTransition(cid, transitionHandle);
else
{
/* set duration */
CGSInvokeTransition(cid, transitionHandle, duration);
}
All that remains now is to work out the format of transitionSpec. After a little trial and error we find the following code works just as well as our initial code:
CGSConnectionRef cid; int transitionHandle; int transitionSpec[5]; cid = _CGSDefaultConnection(); /* Set up the transition specification */ memset(transitionSpec, 0, 20); /* 20 = 0x14 */ transitionSpec[1] = transition; transitionSpec[2] = direction; /* Set up the transition itself */ CGSNewTransition(cid, transitionSpec, &transitionHandle); /* Switch desktop */ CGSSetWorkspace(cid, workspaceNumber); /* A little pause to let the Window Server sort itself out ... */ usleep(10000); /* ... and fire off the transition */ CGSInvokeTransition(cid, transitionHandle, seconds);Conclusions
The above code was added to Desktop Manager and a version sent to those people with Tiger. To my great relief (and suprise) people reported that transitions were working! There are still problems to be worked out but, because of this bit of reverse engineering, Desktop Manager on Tiger will soon be a reality.
Can we now write some documentation on these new API calls? Indeed we can. A little experimentation has shown that these are not just useful for virtual desktops. Indeed anyone can use them in a full-screen app to provide Keynote-style transitions. Simply call CGSNewTransition() which will freeze the screen, draw your new screen and call CGSInvokeTransition() to reveal it to the user.
About the author:
Rich Wareham is a 3rd year PhD student in the Signal Processing Group of the Cambridge University Engineering Department. When not playing with Geometric Algebra and proving theorems he enjoys learning about technology, experimenting with new Operating Systems and teaching undergraduates about Software Engineering.
If you would like to see your thoughts or experiences with technology published, please consider writing an article for OSNews.
- "Reverse engineering OSX, Page 1/2"
- "Reverse engineering OSX, Page 2/2"


