#!/usr/bin/perl -w
my $usage = qq {usbkbpatch.pl: patch AppleUSBKeyboard.kext (or IOHIDFamily.kext on panther)
		                                       (c) heiko\@snark.de 2003
License: absolute freeware - do whatever you want. No warranties! got the idea
from http://opendarwin.org/cgi-bin/cvsweb.cgi/src/IOUSBFamily/AppleUSBKeyboard/
usage: 
usbpatch.pl {USBCode ADBCode} ... : patch each such pair into the keytable
some useful USBCodes are:
  0xe0: left control           
  0xe1: left shift
  0xe2: left option/alt (apple) or command (wintel)
  0xe3: left command (apple) or alt (wintel)
  0xe4: right control
  0xe5: right shift
  0xe6: right option/alt (apple) or command (wintel)
  0xe7: right command (apple) or alt(gr) (wintel)
  0x65: wintel context-menu
some useful ADBCodes are:
  0x3b: control,   0x38: shift  0x3a: option/alt  0x37: command
example (swap right cmd/alt only): $0 0xe6 0x37 0xe7 0x3a
must run as root! PATCHES YOUR OS AT A VERY LOW LEVEL! USE AT YOUR OWN RISK!
} ;

# on a wintel USB  keyboard i use 
# 0xe6 0x34 (right alt to enter) (for the myCommand fake meta patch)
# 0x65 0x3a (w95 to alt) 
# 0x64 0x32 (^ to <) 
# 0x35 0x0a (< to ^) 
# 0x53 0x75 (ins to del)
# sometimes 0xe7 0x34 (right cmd to enter on iMac usb keyboard - no right alt)
# with uCommand: enter->alt and alt->fakeMeta
#
# usbkbpatch.pl 0xe6 0x34 0x65 0x3a 0x64 0x32 0x35 0x0a  (wintel)
# usbkbpatch.pl 0xe7 0x34 0x53 0x75 (iMac usb)

my $param = shift || die $usage ;
if($param =~ /^\-+h/) {
    print $usage;
    exit(0) ;
}

my $ext = "/System/Library/Extensions/IOUSBFamily.kext/Contents/PlugIns/AppleUSBKeyboard.kext" ;
my $usbext = "$ext/Contents/MacOS/AppleUSBKeyboard" ;
my $cosmourl = "http://www.opendarwin.org/cgi-bin/cvsweb.cgi/~checkout~/src/IOUSBFamily/AppleUSBKeyboard/Classes/Cosmo_USB2ADB.cpp" ;
my $cosmo = "Cosmo_USB2ADB.cpp" ;

# generated with -gencosmo and then pasted here...
# this is the octet sequence to find in the binary driver
$srccode = qq{
    0x00 0x00 0x00 0x00 0x00 0x0b 0x08 0x02 0x0e 0x03 0x05 0x04 0x22 0x26 0x28 0x25
   0x2e 0x2d 0x1f 0x23 0x0c 0x0f 0x01 0x11 0x20 0x09 0x0d 0x07 0x10 0x06 0x12 0x13
   0x14 0x15 0x17 0x16 0x1a 0x1c 0x19 0x1d 0x24 0x35 0x33 0x30 0x31 0x1b 0x18 0x21
   0x1e 0x2a 0x2a 0x29 0x27 0x32 0x2b 0x2f 0x2c 0x39 0x7a 0x78 0x63 0x76 0x60 0x61
   0x62 0x64 0x65 0x6d 0x67 0x6f 0x69 0x6b 0x71 0x72 0x73 0x74 0x75 0x77 0x79 0x7c
   0x7b 0x7d 0x7e 0x47 0x4b 0x43 0x4e 0x45 0x4c 0x53 0x54 0x55 0x56 0x57 0x58 0x59
   0x5b 0x5c 0x52 0x41 0x0a 0x6e 0x7f 0x51 0x69 0x6b 0x71 0x00 0x00 0x00 0x00 0x00
   0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x01
   0x03 0x02 0x00 0x00 0x00 0x5f 0x00 0x5e 0x00 0x5d 0x00 0x00 0x00 0x00 0x00 0x0
   0x68 0x66 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
   0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
   0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
   0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
   0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
   0x3b 0x38 0x3a 0x37 0x3b 0x38 0x3a 0x37 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
   0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
} ;

my $panthermode = 0 ;

if(`uname -r` =~ /^7/) { # panther?
    print("using panther settings\n") ;
    $panthermode = 1 ;
    $cosmourl = "http://www.opendarwin.org/cgi-bin/cvsweb.cgi/~checkout~/src/IOHIDFamily/IOHIDFamily/Cosmo_USB2ADB.c?rev=1.1.1.1&content-type=text/plain" ;
    $cosmo = "Cosmo_USB2ADB.c" ;
    $ext = "/System/Library/Extensions/IOHIDFamily.kext" ;
    $usbext = "$ext/Contents/MacOS/IOHIDFamily" ;

    # pantherized...
    $srccode = qq{
	0x80 0x80 0x80 0x80 0x00 0x0b 0x08 0x02 0x0e 0x03 0x05 0x04 0x22 0x26 0x28 0x25 
	0x2e 0x2d 0x1f 0x23 0x0c 0x0f 0x01 0x11 0x20 0x09 0x0d 0x07 0x10 0x06 0x12 0x13 
	0x14 0x15 0x17 0x16 0x1a 0x1c 0x19 0x1d 0x24 0x35 0x33 0x30 0x31 0x1b 0x18 0x21 
	0x1e 0x2a 0x2a 0x29 0x27 0x32 0x2b 0x2f 0x2c 0x39 0x7a 0x78 0x63 0x76 0x60 0x61 
	0x62 0x64 0x65 0x6d 0x67 0x6f 0x69 0x6b 0x71 0x72 0x73 0x74 0x75 0x77 0x79 0x7c 
	0x7b 0x7d 0x7e 0x47 0x4b 0x43 0x4e 0x45 0x4c 0x53 0x54 0x55 0x56 0x57 0x58 0x59 
	0x5b 0x5c 0x52 0x41 0x0a 0x6e 0x7f 0x51 0x69 0x6b 0x71 0x6a 0x80 0x80 0x80 0x80 
	0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x4a 
	0x48 0x49 0x80 0x80 0x80 0x5f 0x80 0x5e 0x80 0x5d 0x80 0x80 0x80 0x80 0x80 0x80 
	0x68 0x66 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 
	0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 
	0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 
	0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 
	0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 
	0x3b 0x38 0x3a 0x37 0x3e 0x3c 0x3d 0x36 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 
	0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80 
    } ;

}

my $code = "" ;

if($param eq "-gencosmo") {
    # read the source of cosmo
    print("retrieving $cosmo\n") ;
    system("curl -o $cosmo '$cosmourl'")
	unless -e $cosmo ;

    die "can not read $cosmo\n" unless open(RH, "<$cosmo") ;
    my $src = join("", <RH>);
    close(RH) ;
    
    $src =~ s/DEADKEY,/0x80,/g ; # for the new Panther/darwin7 sources

    # turn the keytable into a binary sequence of octets
    my $code = "" ;
    while ($src =~ s/\n\s+0x([0-9a-fA-F]{1,2})\,\s+//) {
	$code .= chr(hex($1)) ;
    }
    for($i=0; $i<length($code); $i++) {
	print("\n") if (($i/16) !~/\./) ;
	printf("0x%0.2x ", ord(substr($code, $i, 1))) ;
    }
    printf("\n") ;
    exit(0) ;
}



# i'm sure, pack() can do this more elegant...
while($srccode =~ s/0x([0-9a-f]{2})\s+//) {
    $code .= chr(hex($1)) ;
}


if($param eq "-findcosmo") {
    # hack to find the cosmo table somewhere inside the panther installation
    my $dir = shift || "." ;
    $dir =~ s/\/$//; 
    findcosmo($dir) ;
    exit(0) ;
}


my $osver = `uname -sr` ;
chomp $osver ;
$osver =~ s/[^a-zA-Z0-9\-\.]/_/g ;

# create a backup of the original kext binary
if(! -e "$usbext.$osver") {
    die "can not make a backup of $usbext" if system("cp $usbext $usbext.$osver");
}

# read the kext
if(! open(RH, "<$usbext.$osver")) {
    die "can not read $usbext.$osver\n" ;
}
my $kext = "" ;
while(sysread(RH, $a, 2048)) {
    $kext .= $a ;
} 
close(RH) ;

my $cut = 255 ;
my $index = -1 ;
# they differ at pos 108 - for whatever reason - enough to identify the offset
#$code = substr($code, 0, 107) ;

while($index == -1 && $cut > 30) {
    # find the patch offset
    $code = substr($code, 0, $cut) ;
    $index = index($kext, $code) ;
    $cut-- ;
}

print("offset = $index for a match of $cut bytes\n") ;
die "can not find patch offset" if($index == -1) ;

die "ambiguous patch - at least two possible locations found: $index or " 
    . index($kext, $code, $index+1)
    if(-1 != index($kext, $code, $index+1)) ;

# read the commandline parameters (pairs of position+value in hex)
my %patchtab = () ;
while(defined($param) && $param ne "skip") {
    my $pos = $param ; $pos = hex($1) if($pos =~ /0x(.+)/) ;
    my $val = shift || die $usage ;
    $val = hex($1) if($val =~ /0x(.+)/) ;
    $patchtab{$pos} = chr($val) ;
    $param = shift || undef
}

# rewrite the patched version
open(RH, "<$usbext.$osver") || die "what? coult read $usbext.$osver before" ;
printf("reading from $usbext.$osver\n") ;
open(WH, ">$usbext") || die "can not write to $usbext" ;
printf("writing to $usbext\n") ;
my $count = 0 ;
while($count < $index) {
    $rd = sysread(RH, $a, $index-$count) ;
    die "failed reading..." unless $rd ;
    $count += $rd ;
    syswrite(WH, $a) ;
}
for($i=0; $i<256; $i++) { # copy/patch the actual table
    if(sysread(RH, $a, 1)) {
        if(defined($patchtab{$i})) {
	    $a = $patchtab{$i} ;
	    print("patching position $i to ", ord($a), " at $count\n") ;
	}
	syswrite(WH, $a) ;
	$count++ ;
    } else {
        die "read failed" ;
    }
}
while($rd = sysread(RH, $a, 2048)) { # copy the rest of the kext
    syswrite(WH, $a) ;
    $count += $rd ;
}
close(RH) ;
close(WH) ;
print("wrote $count bytes while copying...\n") ;

if($panthermode) {
    `touch /Library/Extensions` ;
    # from http://www.macosxhints.com/article.php?story=20031207012226892
    `rm -r /System/Library/Caches/com.apple.kernelcaches/` ;
    `rm /System/Library/Extensions.kextcache ` ;
    
    print("you eed to reboot in order to activate the patched driver on panther\n") ;
} else {
  # inject it into the system
  print STDERR "kext could not be unloaded" if system("chmod a+x $ext; kextunload $ext") ;
  print STDERR "kext could not be loaded" if system("kextload $ext") ;
}


### end


sub findcosmo {
    my $dir = shift ;
    print("check dir $dir\n") ;
    opendir(DH, $dir) || die " can not read $dir" ;
    my @list = readdir(DH) ;
    closedir(DH) ;
    my $d ;
    foreach $d (@list) {
        next if ($d =~ /^\.+$/) ;
        $d = "$dir/$d" ;
        if(-d $d) {
            findcosmo($d) ;
        } else {
            checkfile($d) ;
        }
    }
}


sub checkfile {
    my $f = shift ;
#    print("check file $f\n") ;
    if(! open(RH, "<$f")) {
        die "can not read $f\n" ;
    }
    my $body = "" ;
    my $a = "" ;
    while(sysread(RH, $a, 2048)) {
        $body .= $a ;
    } 
    close(RH) ;

    my $template = substr($code, 0, 64) ; # just find the first 100 chars...
    my $index = index($body, $template) ;
    if($index != -1) {
        print("FOUND cosmo at $index in $f\n") ;
        exit(0) ;
    }
}
