sábado, 22 de octubre de 2011

iPad y jailbreak: mi experiencia

Después de la frustrante experiencia con la actualización de mi iPad 1, ya comentada en un post anterior, he de comentar cómo me fue con el jailbreak.

Con RedSn0w es una operación sencilla y relativamente rápida, pero la versión actualmente disponible para iOS 5 es de tipo tethered. Esto quiere decir ni más ni menos que, cada vez que reiniciemos el dispositivo, habremos de tenerlo conectado al ordenador. De lo contrario, olvidémonos del correo y de Safari. iBooks es otra historia, como veremos. En cualquier caso, nos encontramos aquí el primer inconveniente.


Con iBooks sucede que, al menos en la versión b4 de RedSn0w, deja de funcionar. Quizá en versiones posteriores ya se incorpore algún fix. Sea como sea, yo me vi con iBooks fuera de combate. Segundo inconveniente.

Buscando una solución, leí acerca de un parche descargable desde uno de los repositorios accesibles mediante Cidia. Cuando intenté conectarme a él, recibí un mensaje acerca del repositorio en cuestión. Al parecer, publican material sujeto a derechos de autor sin la debida retribución al creador original. Son unos piratas, vaya. Fíjate por dónde, no me sorprendió.

¿Qué hice? Restauré mi iPad. Al fin y al cabo, no soy un fanático de la personalización; excepto por la historia de los nuevos gestos, las posibilidades que me ofrece el iPad "de serie" me convencen razonablemente; además, todo lo que he necesitado hasta ahora lo he encontrado en AppStore, sin que me importe pagar unos cuantos euros por las aplis que uso. No todo va a ser gratis en esta vida -casi nada bueno lo es, y el propio iPad tampoco lo regalan-. Por otro lado, los precios de las aplis son realmente asequibles: la más cara que tengo no llegó a los ocho euros.

En todo este mundillo del jailbreak hay muchos genios -Comex, MuscleNerd- y muchos buenos profesionales que simplemente exploran las posibilidades de un dispositivo sin vulnerar ningún derecho de copyright. Se trata, más que nada, de evadirse de las draconianas reglas del juego impuestas por Apple, con su monotemática obsesión por el control total del ecosistema generado en torno a sus atractivos productos.

Junto a éstos surgen indefectiblemente los sinvergüenzas que, haciendo alarde de una dureza facial pasmosa, tratan de sacar tajada, cuando no intentan persuadirnos de que sus raterías obedecen a elevados ideales de lucha contra los monopolios, difusión libre de la cultura y demás. Basura, en definitiva.

Yo vivo de mi trabajo; los señores que programan con Cocoa Touch, del suyo; los de Cupertino ídem de lienzo, y así sucesivamente. Por eso, tenemos que respetar un poquito el trabajo de los demás, ¿no?

Sea como fuere, lo que yo seguía necesitando era una forma de activar los nuevos gestos de cuatro y cinco dedos en mi iPad 1. Sólo eso, sin Cidia ni jailbreak. Pues es posible, como demuestran en este enlace. Todo el mérito es de alguien que firma D.B., si bien se apoya en el RedSn0w de MuscleNerd.

La idea es tan buena como sencilla. Para activar los multigestures basta cambiar una entrada en un xml de configuración. La dificultad es que dicho xml se encuentra en la carpeta system del iPad y, por tanto, no está accesible a menos que se disponga de privilegios root. Pero esto se consigue desde el modo DFU del dispositivo en los primeros pasos del jailbreak.

En lugar de inyectar el kernel modificado, instalar Cidia, mover las aplis, etc. ¿Por qué no limitarse simplemente a modificar el fichero de configuración necesario, sin tocar nada más? No me digáis, amigos, que no es bonito y elegante :-D

El código de DB, donde se ve claramente el cambio realizado en el fichero plist correspondiente, es interesante y muy didáctico. Os lo reproduzco a continuación.



/*
 *  Gestures tweak
 *  Replacement for jailbreak executable in redsn0w ramdisk
 *
 *  - dB
 */

#include 
#include 
#include 
#include 
#include 
#include 

static int execute ( int *status, const char *command, char *arguments[] ) {
    // Fork the process
    int result = fork ();
    if ( result == -1 ) {
        perror ( "fork" ); return -1;
    }
    // Child process
    else if ( result == 0 ) {
        execv ( command, arguments );
        perror ( "execv" );
        exit(-1);
    }
    // Parent process
    else {
        // Wait for the child process to end
        if ( waitpid ( result, status, 0 ) == -1 ) {
            perror ( "waitpid" ); return -1;
        } return 0;
    }
}

static int executev ( int *status, const char *command, ... ) {
    // Argument list
    int argc = 0;
    char* argv[512];

    // Setup variable arguments
    va_list vargs;
    va_start ( vargs, command );

    // Add passed arguments to the list
    while ( argc++ < (sizeof(argv)/sizeof(argv[0])) ) {
        int i = argc - 1;
        argv[i] = va_arg ( vargs, char* );
        if ( !argv[i] ) break;
    } argv[argc] = NULL;

    // Execute the command
    int result = execute ( status, command, argv );

    // Cleanup and return the result
    va_end ( vargs );
    return result;
}

static int modifycapability ( const char *capability, const char *device, int enable ) {
    // Setup the path
    char path[PATH_MAX];
    snprintf ( path, sizeof ( path ), "/System/Library/CoreServices/SpringBoard.app/%s.plist", device );

    // Perform the operation and return the result
    int status = 0;
    int result = executev ( &status, "/plutil", "/plutil",
        "-key", "capabilities",
        "-key", capability,
        enable ? "-true" : "-false",
        path, NULL
    ); return ( result != 0 || !(WIFEXITED(status) && WEXITSTATUS(status) == 0) ) ? -1 : 0;
}

static int copy ( const char *sourcepath, const char *targetpath ) {
    // The reading buffer
    char buffer[512];

    // Open the source and target files
    FILE* source = fopen ( sourcepath, "rb" );
    if ( !source ) { perror ( "fopen" ); return -1; }
    FILE* target = fopen ( targetpath, "wb" );
    if ( !target ) { fclose ( source ); perror ( "fopen" ); return -1; }

    // Perform the copy
    for ( ;; ) {
        int count = fread ( buffer, 1, sizeof ( buffer ), source );
        if ( count <= 0 ) break;
        fwrite ( buffer, 1, count, target );
    }

    // Cleanup
    fclose ( source );
    fclose ( target );
    return 0;
}

static int replace ( const char *path, const char *pattern, const char *replacement ) {
    // Open the source and target files
    FILE* source = fopen ( path, "rb" );
    if ( !source ) { perror ( "fopen" ); return -1; }
    FILE* target = fopen ( "/tmp/replacement", "wb" );
    if ( !target ) { fclose ( source ); perror ( "fopen" ); return -1; }

    // Initial allocations and length calculations
    int repllen = strlen ( replacement );
    int pattlen = strlen ( pattern );
    int bufflen = pattlen * 5;
    char* buffer = malloc ( bufflen + 1 );

    // Perform the search and replace
    for ( ;; ) {
        int count = fread ( buffer, 1, bufflen, source );
        if ( count < pattlen ) {
            fwrite ( buffer, 1, count, target ); break;
        }

        int i; for ( i = 0; i <= count - pattlen; i++ ) {
            // If a match is found
            if ( memcmp ( buffer + i, pattern, pattlen ) == 0 ) {
                fwrite ( replacement, 1, repllen, target );
                i += pattlen - 1;
            }
            // No match was found
            else {
                fwrite ( buffer + i, 1, 1, target );
            }
        } fwrite ( buffer + i, 1, count - i, target );
    }

    // Cleanup
    fclose ( source );
    fclose ( target );
    free ( buffer );

    // Copy
    copy ( "/tmp/replacement", path );
    unlink ( "/tmp/replacement" );
    return 0;
}

int main () {
    // Op status
    int status = 0;

    // Greeting message
    printf ( "\n" ); // Initial blank line
    printf ( "Gestures & display mirroring for iPad 1 (w/o Jailbreak)\n"
             "by dB\n\n" ); fflush ( stdout );

    // Attempt to update SpringBoard capabilities
    printf ( "Updating SpringBoard capabilities...\n" );
    printf ( "Enabling multitasking-gestures...\n" );
    printf ( "%s\n",
        modifycapability ( "multitasking-gestures", "K48AP", 1 ) == 0 ? "OK" : "FAIL" );
    printf ( "Enabling display-mirroring...\n" );
    printf ( "%s\n",
        modifycapability ( "display-mirroring", "K48AP", 1 ) == 0 ? "OK" : "FAIL" );

    // Convert /Applications/Preferences.app/General.plist into XML format
    printf ( "Converting Preferences/General.plist into XML...\n" );
    status = executev ( NULL, "/plutil", "/plutil",
        "-xml", "/Applications/Preferences.app/General.plist", NULL
    ); printf ( "%s\n", (status == 0) ? "OK" : "FAIL" );
    
    // Patch the file, correcting the misspelled "Mutltitasking_Gesture" to "Multitasking_Gesture"
    printf ( "Patching Preferences/General.plist...\n" );
    status = replace ( "/Applications/Preferences.app/General.plist", "Mutltitasking_Gesture", "Multitasking_Gesture" );
    printf ( "%s\n", (status == 0) ? "OK" : "FAIL" );

    // Convert /Applications/Preferences.app/General.plist back into binary format
    printf ( "Converting Preferences/General.plist back into binary...\n" );
    status = executev ( NULL, "/plutil", "/plutil",
        "-binary", "/Applications/Preferences.app/General.plist", NULL
    ); printf ( "%s\n", (status == 0) ? "OK" : "FAIL" );

    // Perform a disk sync
    printf ( "\n" );
    printf ( "Performing a sync... " ); fflush ( stdout );
    sync (); printf ( "OK\n" );

    // Countdown to reboot
    printf ( "\n" );
    printf ( "Rebooting in... " );
    int i; for ( i = 10; i >= 1; i-- ) {
        printf ( "%d ", i ); fflush ( stdout );
        sleep ( 2 );
    } printf ( "0!\n" );

    // Reboot the device
    reboot ( 0 );
    return EXIT_SUCCESS;
}

Y funciona. Vaya que si funciona...

Saludos

No hay comentarios: