12 Feb 2006

circumventing ichat's g3 restriction (part 1)

[UPDATE (13 Feb 06): turns out that canSupportH264Decode is not really what I want because you cannot use H264 with any of the G3 processors. That turned out to be a red herring. Instead, what we really wanted was the ProcessorSpeedMHz which we can override using mach_inject. More on that in a following blog post.]

so the newest ichat will not enable video chat for G3's that are slower than 600MHz. the reason is that i suppose the H264 implementation requires quite a bit more oomph than these G3s can provide. however, the problem is that it is a blanket ban that assumes you are using it for two way chat. if you are only doing one way video chat, you should be allowed to use it for that. I mean, theoretically speaking that should only require a 300MHz G3! (ok, i know my numbers are flawed, but my argument still stands.)


what got me thinking is that the software from iglasses and ichatusbcam is able to circumvent this restriction. however, i don't really need iglasses or ichatusbcam, i just want to switch off the stupid restriction. since they can do it, it must be quite easy to do. i don't really need to fork out some money just to turn it off, i'm sure its only a couple of lines of code once i found the right place.

in fact, after some educated guesses, i think i have the solution. but first i'll talk about finding where this restriction is set so that maybe other people who might like to think about trying the same thing won't fall into the same holes as i did.

Step 1. Collect the tools and links.

If you are just starting out, read the wonderful CocoaReverseEngineering guide by Mike Solomon, the developer behind PithHelmet.

Basically, most of the essential tools are already on your mac, otool, nm and gdb. The only other essential tool you need is class-dump which is able to scour for symbols in a Mach-O binary and dump a pseudo header for you. That way you can find out the Objective-C selectors that an object responds to, and also their names. One note of caution, you will need to get class-dump 3.1 which handles universal binaries if you are doing this on Mac OS X 10.4.

Step 2. Know your enemy

My enemy is iChat and it is rather simple. Here are the steps I took, some leading to dead ends, others leading to fruitful places.

Firstly, when you plug an iSight into a G3, you don't get your usual video preview, instead you get a message saying that "Your computer does not support video conferencing." So it must of done the check somewhere in the program to determine that. Now, obviously it needs to find the clock speed and the processor type. Under Mac OS 9/10, there is only one good way to do it, that is using Gestalt. You can find out all sorts of information all the way from whether your processor is a M68K to a G5 (and presumably Intel.)

Knowing that Gestalt will be called at some point, now I just need to make sure that it really is what I'm looking for. I use nm to confirm this for me:

nm /Applications/iChat.app/Contents/MacOS/iChat | grep Gestalt
U _Gestalt

So it is there, the U there says that it is undefined. That means it get resolved at runtime, so its somewhere in some shared library.

Next step I took was the run class-dump on iChat and look through the class definitions to see if there was anything matching "Clock", "Video" or "Processor" to see if there was a specific function that looked at the clock speed or processor. I couldn't find any, weird.

Not to worry, my next plan of attack is to run iChat through GDB. It is kinda scary at first, but once you know a couple of the commands, its pretty easy. What we want to do is to find out where Gestalt is being called and look at the stack trace that leads to that call. Because Gestalt is used to query many aspects of the system, there maybe alot of calls to this function, so smart and filter out what is unneeded and take note of what is relevant.

gdb /Application/iChat.app/Contents/MacOS/iChat
(gdb) break Gestalt
Function "Gestalt" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (Gestalt) pending.
(gdb) run

Now it will run, and every time Gestalt is called, it will jump you to a prompt. There are many things you can do, but the simplest thing to do is to print out the back trace using the command backtrace or its shorthand
bt. When you've had enough with the back trace, use the command continue or the shorthand c and it will continue running the program from that point. I'll just show you the interesting bits that you'll find when you run it in that way:

(gdb) r
Starting program: /Applications/iChat.app/Contents/MacOS/iChat
Reading symbols for shared libraries ....
Breakpoint 1, 0x90b1a470 in Gestalt ()
(gdb) bt
#0 0x90b1a470 in Gestalt ()
#1 0x9814155c in ProcessorSpeedMHz ()
#2 0x98141490 in getHardwareInfo ()
#3 0x98141368 in +[VCRules canSupportH264Decode] ()
#4 0x98141228 in +[VCRules canSupportH264Encode] ()
#5 0x98141124 in +[VCRules bestParametersForFramerate:imageSize:forTransportType:] ()
#6 0x98140e70 in -[VideoConferenceController resetVars] ()
#7 0x9813f990 in -[VideoConferenceController initWithRectTexture:] ()
#8 0x9813f71c in -[VideoConferenceMultiController initWithRectTexture:] ()
Breakpoint 1, 0x90b1a470 in Gestalt ()
(gdb) bt
#0 0x90b1a470 in Gestalt ()
#1 0x9814181c in MachineType ()
#2 0x98141584 in hasG5 ()
#3 0x9814149c in getHardwareInfo ()
#4 0x98141368 in +[VCRules canSupportH264Decode] ()
#5 0x98141228 in +[VCRules canSupportH264Encode] ()
#6 0x98141124 in +[VCRules bestParametersForFramerate:imageSize:forTransportType:] ()
#7 0x98140e70 in -[VideoConferenceController resetVars] ()
#8 0x9813f990 in -[VideoConferenceController initWithRectTexture:] ()
#9 0x9813f71c in -[VideoConferenceMultiController initWithRectTexture:] ()

[UPDATE (13 Feb 06):Please note that the below is one of the dead ends. canSupportH264Encode is not what we need to enable iChat for G3s under 600MHz, but is probably also an interesting call by itself.]

There we have something interesting, it seems like the critical thing that determines whether you can use video conference is that [VCRules canSupportH264Encode]. But what is this VideoConferenceController?
The obvious place is in iChat.app, but its no where to be found in the classes. So it must of been loaded in somewhere else through a framework. Here we break out otool and check out what libraries it uses:

otool -L /Applications/iChat.app/Contents/MacOS/iChat | grep Video
/System/Library/PrivateFrameworks/VideoConference.framework/Versions/A/VideoConference (compatibility version 2.0.0, current version 2.0.0)

Here we see there is a PrivateFramework that handles all the VideoConference joy. Running class-dump on this framework's library will reveal the VCRules class. And that is what we need to modify.

lass-dump /System/Library/PrivateFrameworks/VideoConference.framework/VideoConference | grep VCRules -A 20
@interface VCRules : NSObject

+ (unsigned int)QuickTimeBandwidth;
+ (id)rulesForVideoCodec:(int)fp8 withCurrentUserCount:(int)fp12 withBitrateLimit:(int)fp16;
+ (id)rulesForH263WithCurrentUserCount:(int)fp8 withBitrateLimit:(int)fp12;
+ (id)rulesForH264WithCurrentUserCount:(int)fp8 withBitrateLimit:(int)fp12;
+ (void)loadOtherRules;
+ (void)parametersForRules:(id)fp8 pFramerate:(int *)fp12 pWidth:(int *)fp16 pHeight:(int *)fp20;
+ (BOOL)canSupportH264Encode;
+ (BOOL)canSupportH264Decode;
+ (void)bestParametersForFramerate:(int *)fp8 imageSize:(int *)fp12 forTransportType:(int)fp16;
+ (BOOL)isHardwareSuitableForFilters;
+ (void)updateImageSize:(int *)fp8 forBandwidth:(int)fp12;
+ (unsigned int)minAudioBandwidth;
+ (unsigned int)minVideoBandwidth;
+ (unsigned short)cutoff15FPS;
+ (unsigned int)magicRTCBandwidth;
+ (unsigned int)minCPUSpeedForH264;
+ (struct _NSSize)applyImageSizeDowngradeRuleForPayload:(int)fp8 currentSize:(struct _NSSize)fp12 bitrate:(int)fp20 pFramerate:(int *)fp24 inMultiway:(BOOL)fp28;

There are the nice bits and pieces we can override. All sorts of limitations we can get rid of. They're probably there for a good reason, but we can act like children and think that we know better than Apple Engineers! Emm ..

Step 2.5 Alert Alert! Dead end.

[UPDATE (13 Feb 06):This section added because more testing revealed that canSupportH264Encode was not the function that detemines whether a G3 under 600MHz can run video chat.]

Further testing actually indicates that canSupportH264Encode is a red herring because G3s do not use H264, but instead H263. So enabling support for that by overriding/replacing those methods won't actually help our cause. Instead, we should look at the interesting function that it called called ProcessorSpeedMHz. If you debug a little more, you'll find that iChat will also call a function called HasAltivec. Interestingly, Apple's sample code online has a sample implementation of HasAltivec which is solely to detect whether the processor is a G4.

If the processor does not have Altivec it must be a G3 or less. In that case, iChat uses are different mechanism to determine whether video chat should be enabled, which is just by comparing ProcessorSpeedMHz to the reference.

As a side note, checking minCPUSpeedForH264 will show you that you need a 850MHz G4 or above to use H264. In fact, a bit more hunting around and you can actually unlock the restriction that only allows certain Dual G4s and G5s to host multi-way voice and video conference. But that'll be for another day.

Step 3. Modifying iChat's Behaviour at Runtime

[UPDATE (13 Feb 06):Added pointer to the "real" method we would like to pursue, which is mach_override rather than MethodSwizzling.]

I'll go into that in the next post, but basically it involves SIMBL, MethodSwizzling (with a simple twist since these are all Objective C class selectors rather than instance selectors) and/or mach_override (kind of like LD_PRELOAD for Linux). Stay tuned!

You can reply to me about this on Twitter: