Hace poco un USER de esta comunidad me preguntó acerca del modo de almacenamiento de los Miis en el mando, os tengo que reconocer que era algo que desconocía por completo. Era nuevo para mí incluso la posibilidad de copiar un Mii al mando. Esto inmediatamente despertó en mí una gran curiosidad por ver cuál era el mecanismo de copia y de almacenamiento. El presente tutorial trata de la estructura de datos de los Miis, como se almacenan en la NAND, y como se transfieren y almacenan posteriormente en el mando.
Quizás no sea un tutorial tan vistoso como el anterior de la Wiiboard, pero he preferido centrarme en lo sustancial del tema de los Miis, sin tener que sobrecargar el tutorial explicando las GX en 3D. Creo que bastante compleja es ya la programación para leer los datos desde la NAND y desde el mando como para complicarla con otras cosas. Si seguís el curso, el resultado será que conseguiréis hacer un programa que lee datos de todos los Miis que tenemos copiados en los mandos o en la memoria de la Wii (NAND) y los muestra en pantalla.
Si os atrevéis os dejaré pistas de cómo se podría copiar del mando a la Wii y viceversa, pero tened mucho cuidado en hacerlo, yo no os lo recomiendo, ya que tocar la NAND puede acabar en un Brick.
Fuentes del curso Enlace externo
Biblioteca IFSF Enlace externo
Lo primero que debemos hacer para poder leer un Mii de un mando es, evidentemente, tener uno en el mando y para ello no conozco otro método que el de copiarlo desde la Wii mediante el Canal Mii.
Se pueden tener hasta un total de 10 Miis en cada mando, y la forma de copiarlos es la siguiente:
Ya tenemos los Miis dentro del mando, pero ¿Cómo se almacenan estos 10 Miis en la memoria del Wiimote?. Seguidamente vamos a ver el formato de datos, tanto del bloque completo como de cada Mii individual.
Los datos de los Miis se guardan en la memoria del Wiimote en dos bloques de 752 bytes , comenzando en la posición 0x0FCA, incluyendo en cada una copia idéntica de la información del otro bloque con datos de 10 Miis y 2 bytes de CRC.
Cuando leemos los dos bloques, calculamos el CRC del primer bloque y lo comparamos con el CRC almacenado, si no coinciden utilizamos los datos del segundo bloque. Si el CRC del segundo bloque también falla los datos del Mii serán inválidos y no podrán ser utilizados.
El formato de cada bloque es como sigue:
Podemos ver que el bloque comienza con 4 bytes que contienen la versión del software de Miis, seguidos de 2 bytes con información de los Miis que son miembros del desfile de Miis. Después hay 2 bytes con función desconocida. Seguidamente nos encontramos con los datos de los Miis propiamente dichos. Estos consisten en un array de 10 bloques de 74 bytes. Luego tenemos dos más desconocidos y por último los 2 bytes correspondientes al CRC.
Para el cálculo del CRC se utiliza el estándar CRC-16 CCITT con un polinomio 0x1021 y un valor de comienzo de 0x0000.
El formato de cada uno de los 10 bloques de 74 bytes que contienen la información de cada Mii queda como sigue:
En total 0x4A=74 bytes por cada Mii.
Algunos comentarios acerca del formato de los Miis son los siguientes:
Los Miis que hay guardados en la memoria de la Wii (NAND) están ubicados en el archivo: isfs://shared2/menu/FaceLib/RFL_DB.dat.
En este archivo caben un máximo de 100 Miis y el formato en el que se almacenan es idéntico al formato en el que están guardados en el mando.
El contenido del archivo es exactamente el siguiente:
Llegados a este punto en el que ya sabemos cómo se almacenan los Miis, tanto en la memoria de la consola como en la memoria del mando, debemos comenzar a pensar cómo podemos plasmar esos conocimientos en un programa. Recordando lo que decíamos al principio del tutorial, nuestra intención es hacer un programa que nos permita leer la información de los Miis desde cualquiera de los mandos y también desde la memoria, por lo que necesitaremos una manera de comunicarnos con ambas cosas.
Comenzaremos abordando la comunicación con los mandos.
Si habéis echado un ojo al wpad.h supongo que os habréis dado cuenta que esta biblioteca no cuenta con ninguna función que nos permita enviar un bloque de datos a un mando, a excepción de la función que nos permite enviar un sonido PCM. Es por ello que, si queremos escribir en la posición 0x0FCA del mando en cuestión necesitamos utilizar otro medio. La única solución que he encontrado con las bibliotecas estándar que incluye el devkitpro es utilizar directamente Wiiuse.
Wiiuse es la biblioteca de bajo nivel en la que se apoya Wpad para acceder a los mandos. Wpad no es más que una capa por encima de wiiuse que nos facilita enormemente la vida. Utilizar Wiiuse no es nada sencillo, ya que todo lo que hace Wpad sin que nosotros nos demos cuenta, tendremos que ser nosotros quienes lo hagamos.
Como ejemplo, algo tan simple como inicializar con wpad_init(); en wiiuse equivaldría a hacer lo siguiente:
s32 WPAD_Init() { u32 level; struct timespec tb; int i; _CPU_ISR_Disable(level); if(__wpads_inited==WPAD_STATE_DISABLED) { __wpads_ponded = 0; __wpads_active = 0; memset(__wpdcb,0,sizeof(struct _wpad_cb)*WPAD_MAX_WIIMOTES); memset(&__wpad_devs,0,sizeof(conf_pads)); memset(__wpad_keys,0,sizeof(struct linkkey_info)*WPAD_MAX_WIIMOTES); for(i=0;i<WPAD_MAX_WIIMOTES;i++) { __wpdcb[i].thresh.btns = WPAD_THRESH_DEFAULT_BUTTONS; __wpdcb[i].thresh.ir = WPAD_THRESH_DEFAULT_IR; __wpdcb[i].thresh.acc = WPAD_THRESH_DEFAULT_ACCEL; __wpdcb[i].thresh.js = WPAD_THRESH_DEFAULT_JOYSTICK; __wpdcb[i].thresh.wb = WPAD_THRESH_DEFAULT_BALANCEBOARD; __wpdcb[i].thresh.mp = WPAD_THRESH_DEFAULT_MOTION_PLUS; if (SYS_CreateAlarm(&__wpdcb[i].sound_alarm) < 0) { WPAD_Shutdown(); _CPU_ISR_Restore(level); return WPAD_ERR_UNKNOWN; } } if(CONF_GetPadDevices(&__wpad_devs) < 0) { WPAD_Shutdown(); _CPU_ISR_Restore(level); return WPAD_ERR_BADCONF; } if(__wpad_devs.num_registered == 0) { WPAD_Shutdown(); _CPU_ISR_Restore(level); return WPAD_ERR_NONEREGISTERED; } if(__wpad_devs.num_registered > CONF_PAD_MAX_REGISTERED) { WPAD_Shutdown(); _CPU_ISR_Restore(level); return WPAD_ERR_BADCONF; } __wpads = wiiuse_init(WPAD_MAX_WIIMOTES,__wpad_eventCB); if(__wpads==NULL) { WPAD_Shutdown(); _CPU_ISR_Restore(level); return WPAD_ERR_UNKNOWN; } __wiiuse_sensorbar_enable(1); BTE_Init(); BTE_SetDisconnectCallback(__wpad_disconnectCB); BTE_InitCore(__initcore_finished); if (SYS_CreateAlarm(&__wpad_timer) < 0) { WPAD_Shutdown(); _CPU_ISR_Restore(level); return WPAD_ERR_UNKNOWN; } SYS_RegisterResetFunc(&__wpad_resetinfo); tb.tv_sec = 1; tb.tv_nsec = 0; SYS_SetPeriodicAlarm(__wpad_timer,&tb,&tb,__wpad_timeouthandler,NULL); __wpads_inited = WPAD_STATE_ENABLING; } _CPU_ISR_Restore(level); return WPAD_ERR_NONE; }
Y aquí faltarían todas las funciones auxiliares que son llamadas desde WPAD_init().
Pero no os asustéis tan pronto, ya que he encontrado una manera de hacer una pequeña trampa que nos permita evitarnos todo este rollo y que luego veremos. Otra cosa a tener en cuenta a la hora de utilizar wiuse es que, en lugar del sistema habitual de polling, se basa en lo que se conoce como funciones callback.
¿Y qué es eso de las funciones callback? Os preguntaréis.
Una función callback suele usarse cuando accedemos a periféricos cuya comunicación es lenta en comparación con la velocidad de ejecución del procesador. En estos casos, y para permitir que la ejecución del programa continúe mientras esperamos que se termine la comunicación, usamos las funciones callback. Éstas consisten en pasar como parámetro un puntero a otra función que será llamada cuando se termine la ejecución requerida (en nuestro caso cuando se termine la lectura de la memoria del mando). De esta manera nuestro programa continuará su ejecución sin tener que esperar a que la transferencia de datos mediante Bluetooth termine, evitando un bloqueo temporal del programa principal.
La representación gráfica de una lectura de la memoria del mando con este sistema sería como sigue:
Vamos con el programa.
Los enlaces para descargaros los fuentes del programa los tenéis al principio del tutorial.
También tenéis al principio el enlace para descargaros la biblioteca IFSF. Para instalarla debéis descomprimir el archivo en la misma unidad de disco donde tengáis instalado el devkitpro.
La estructura básica del programa que vamos a utilizar es la siguiente:
Iniciar Video
Iniciar Mandos
Leer los Miis de la NAND
Mientras no se pulse Home hacer
Gestionar eventos
Si algún Mando seleccionado leer Miis del mando
Si hay datos disponibles mostrarlos por pantalla.
Fin de programa
Recorriendo el código resultante del main nos encontramos lo siguiente:
Inicialización del video para que podamos usarlo en modo texto, tal y como hacíamos en el primer tutorial.
Init_Video();
Inicialización de WPAD.
WPAD_Init();
¿Inicialización de WPAD?. Al final resulta que podemos llamar a WPAD_init de todas formas y no se produce ninguna interacción con nuestro código. WPAD_Init inicializará el Bluetooth, las interrupciones del procesador etc, sin tener que adentrarnos en cómo se hace todo eso.
Esto es lo único que utilizaremos de WPAD ya que de todas formas tendremos que llamar a wiiuse_init para poder utilizar la función de bajo nivel de lectura de la memoria de los mandos que necesitamos, ya que wiiuse_init nos devuelve información que tendremos que utilizar al llamar a dicha función de lectura.
Como uno de los parámetros de wiiuse_init es la función de callback para gestionar los eventos, se desasignará la función interna de eventos que utiliza WPAD y a partir de ahí ya no funcionará ninguna función de WPAD.
Vamos entonces con lo inevitable y continuemos a partir de aquí con wiuse.
A wiiuse_init le pasaremos el número máximo de wiimotes y la función que será llamada cuando se produzca un evento relacionado con los mandos y nos devolverá un array con la información de los mandos que necesitaremos después para la lectura de la memoria del mando.
wiimotes = wiiuse_init(MAX_WIIMOTES, __handle_event);
Seguidamente cargaremos los Miis de la NAND. Esta operación la realizamos una sola vez y fuera del bucle, ya que esta información no es posible que cambie durante la ejecución de nuestro programa.
loadMiis_Wii();
Tras lo que pasamos un bucle principal excepcionalmente vacío ya que prácticamente todo el código se concentra en la función __handle_event que hemos definido arriba.
Reinicia_Pantalla(); while(!terminar) { // Esperamos al siguiente frame if (datos_disponibles) { datos_disponibles = false; Reinicia_Pantalla(); mostrar_datos_miis(); } VIDEO_WaitVSync(); }
Como veis en dicho bucle únicamente se comprueba si hay datos disponibles para mostrar y en caso afirmativo llama a la función correspondiente para mostrar la información de los Miis.
Vamos a entrar en detalle en la función loadMiis_Wii.
void loadMiis_Wii(){ if (ISFS_Initialize() == 0) { // Inicializa el ISFS ISFS_Mount(); // Monta el ISFS char *wiidata; // wiidata = readmii(FACELIB_Wii); // Lee los datos del fichero de datos ISFS_Unmount(); // Desmonta el ISFS if(wiidata) loadMiis(wiidata); // Manda el buffer a la funcion loadMiis } }
En dicha función utilizamos una biblioteca que nos permite acceder al sistema de archivos de la NAND.
Básicamente hacemos lo siguiente:
Vamos a ver qué contiene la función readmii:
// Lee la información de los Miis y la devuelve como buffer char *readmii(char *path){ long Size; FILE *BGM; char *buffer; BGM = fopen(path, "rb"); if (BGM==NULL) { printf("%s No existe\n", path); return NULL; } Size = MII_MAX * MII_SIZE + MII_HEADER; buffer = (char*) malloc (sizeof(char)*Size); fread(buffer, 1, Size, BGM); fclose(BGM); return buffer; }
La función readmii es simple:
Ahora echémosle un vistazo a loadMiis:
Esta función recorre el array de Miis recién leído y copia solo los Miis definidos a un array en memoria para luego poder utilizarlos.
void loadMiis(char *data){ int start; int n = 0; int cur = 0; char * mii; char * buff; NANDmiis = (MII_DATA_STRUCT*) malloc (sizeof(MII_DATA_STRUCT)*100); if (data[0] == 'R' && data[1] == 'N' && data[2] == 'O' && data[3] == 'D'){ // Lee hasta un máximo de 100 Miis que son los que caben en la Wii for (n=0;n < 100;n++){ // Asigna el principio del bloque de datos del siguiente Mii start = n * MII_SIZE + 4; mii = (char*) NANDmiis+n*MII_SIZE; buff = data + start; // Copiamos en Mii actual en el array de la memoria memcpy(mii, buff,MII_SIZE); // Comprobamos que hay algún Mii definido en ese bloque if(!(NANDmiis[cur].eyeType==0 && NANDmiis[cur].eyeColor==0 && NANDmiis[cur].eyeSize==0 && NANDmiis[cur].eyebrowType==0 && NANDmiis[cur].eyebrowRotation==0)) cur++; } // En NoOfMiisInNAND se queda el número de Miis encontrados NoOfMiisInNAND = cur; } else { printf("version the Mii %c%c%c%c incompatible\n", data[0], data[1], data[2], data[3]); } }
Para cada uno de los Miis comprobamos si ese Mii tiene definidos los atributos de tipo de ojos, color de ojos, tamaño de ojos, tipo de cejas y rotación de cejas y si es así lo copia al array de Miis, en caso contrario considera que no es un Mii definido.
Una vez repasada la lectura de los Miis en la NAND vamos a entrar en detalle de la función de gestión de eventos __handle_event.
void __handle_event(struct wiimote_t* wm, int event) { s32 chan; switch (event) { case WIIUSE_CONNECT: chan = wm->unid; wiiuse_set_leds(wm,(WIIMOTE_LED_1<<(chan%WPAD_BALANCE_BOARD)),NULL); break; } /* Si se pulsa A activamos la lectura de datos */ if (IS_PRESSED(wm, WIIMOTE_BUTTON_A)) Leer_Datos = true; if (IS_PRESSED(wm, WIIMOTE_BUTTON_UP)) { Leer_Datos = true; if (origen_de_datos == DESDE_NAND) origen_de_datos = DESDE_WIIMOTE; else origen_de_datos++; } if (IS_PRESSED(wm, WIIMOTE_BUTTON_DOWN)) { Leer_Datos = true; if (origen_de_datos == DESDE_WIIMOTE) origen_de_datos = DESDE_NAND; else origen_de_datos--; } if (IS_PRESSED(wm, WIIMOTE_BUTTON_LEFT)) { Leer_Datos = true; if (wiimote_activo == 0) wiimote_activo = MAX_WIIMOTES; else wiimote_activo--; } if (IS_PRESSED(wm, WIIMOTE_BUTTON_RIGHT)) { Leer_Datos = true; if (wiimote_activo == MAX_WIIMOTES) wiimote_activo = 0; else wiimote_activo++; } if (IS_PRESSED(wm, WIIMOTE_BUTTON_MINUS)) { Leer_Datos = true; if (mii_activo == 0) mii_activo = max_miis; else mii_activo--; } if (IS_PRESSED(wm, WIIMOTE_BUTTON_PLUS)) { Leer_Datos = true; if (mii_activo == max_miis) mii_activo = 0; else mii_activo++; } if (IS_PRESSED(wm, WIIMOTE_BUTTON_HOME)) terminar = true; if (Leer_Datos) { switch (origen_de_datos) { case DESDE_WIIMOTE: Leer_Datos = false; max_miis = NoOfMiisInWiimote; printf("Leyendo wiimote %i.\n", wiimote_activo); if (mii_activo >= max_miis) mii_activo = 0; wiiuse_read_data(wiimotes[wiimote_activo], (ubyte*) &miis, Dir_Bloque, sizeof(miis), handle_read); Dir_Bloque = MII_DATA_ADDR; break; case DESDE_NAND: Leer_Datos = false; printf("Leyendo NAND.\n"); max_miis = NoOfMiisInNAND; if (mii_activo >= max_miis) mii_activo = 0; datos_disponibles = true; break; } } }
Lo primero que nos encontramos es la asignación de los leds del mando.
Esta es otra más de las diferencias con Wpad. Wiiuse no asignará automáticamente el led que le corresponda a cada mando y en su lugar deberemos hacerlo nosotros manualmente.
Si no lo hacemos, los mandos funcionaran correctamente pero con los leds apagados.
switch (event) { case WIIUSE_CONNECT: chan = wm->unid; wiiuse_set_leds(wm, WIIMOTE_LED_1<<(chan%WPAD_BALANCE_BOARD)),NULL); break; }
Lo siguiente que haremos será gestionar las opciones del menú que nos permitirán seleccionar cual es el origen de los datos que vamos a leer.
Las opciones que tendremos en nuestro caso serán las siguientes:
En cualquiera de los casos anteriores asignamos Leer_Datos a “true” para que lea la información del Mii desde el origen de datos seleccionado.
/* Si se pulsa A activamos la lectura de datos */ if (IS_PRESSED(wm, WIIMOTE_BUTTON_A)) Leer_Datos = true; if (IS_PRESSED(wm, WIIMOTE_BUTTON_UP)) { Leer_Datos = true; if (origen_de_datos == DESDE_NAND) origen_de_datos = DESDE_WIIMOTE; else origen_de_datos++; } if (IS_PRESSED(wm, WIIMOTE_BUTTON_DOWN)) { Leer_Datos = true; if (origen_de_datos == DESDE_WIIMOTE) origen_de_datos = DESDE_NAND; else origen_de_datos--; } if (IS_PRESSED(wm, WIIMOTE_BUTTON_LEFT)) { Leer_Datos = true; if (wiimote_activo == 0) wiimote_activo = MAX_WIIMOTES; else wiimote_activo--; } if (IS_PRESSED(wm, WIIMOTE_BUTTON_RIGHT)) { Leer_Datos = true; if (wiimote_activo == MAX_WIIMOTES) wiimote_activo = 0; else wiimote_activo++; } if (IS_PRESSED(wm, WIIMOTE_BUTTON_MINUS)) { Leer_Datos = true; if (mii_activo == 0) mii_activo = max_miis; else mii_activo--; } if (IS_PRESSED(wm, WIIMOTE_BUTTON_PLUS)) { Leer_Datos = true; if (mii_activo == max_miis) mii_activo = 0; else mii_activo++; }
Si la variable LeerDatos es “true” comprobamos el origen de datos y copiamos los datos desde éste si es necesario.
if (Leer_Datos) { switch (origen_de_datos) { case DESDE_WIIMOTE: Leer_Datos = false; max_miis = NoOfMiisInWiimote; printf("Leyendo wiimote %i.\n", wiimote_activo); if (mii_activo >= max_miis) mii_activo = 0; wiiuse_read_data(wiimotes[wiimote_activo], (ubyte*) &miis, Dir_Bloque, sizeof(miis), handle_read); Dir_Bloque = MII_DATA_ADDR; break; case DESDE_NAND: Leer_Datos = false; printf("Leyendo NAND.\n"); max_miis = NoOfMiisInNAND; if (mii_activo >= max_miis) mii_activo = 0; datos_disponibles = true; break; } }
Para el caso de que la lectura sea desde el wiimote deberemos usar la función de wiiuse “wiiuse_read_data”, cuya sintaxis es la siguiente:
int wiiuse_read_data(struct wiimote_t *wm,ubyte *buffer,uint addr,uword len,cmd_blk_cb cb)
Parámetros:
Vamos a ver ahora la función handle_read:
void handle_read(struct wiimote_t* wm, u8* data, unsigned short len) { printf("\n\n--- Leyendo datos del [wiimote id %i] ---\n", wm->unid); printf("Leidos %i bytes.\n", len); datos_disponibles = true; }
Como vemos, dicha función es muy simple y únicamente asigna la variable “datos_disponibles” a “true” para que se muestren los datos desde el main mediante la función mostrar_datos_miis().
// visualiza los Miis dependiendo de la seleccion actual void mostrar_datos_miis(void) { int crc_c; static int bloque = 1; printf("Mii numero %i \n", mii_activo); switch (origen_de_datos) { case DESDE_NAND: printf("Leyendo desde NAND\n"); DisplayMii(NANDmiis[mii_activo]); break; case DESDE_WIIMOTE: crc_c = crc((char*) &miis, sizeof(miis)-2); printf("CRC bloque %i: %x \n", bloque, miis.crc); printf("CRC calculado %x \n", crc_c); if (miis.crc != crc_c) { if (bloque == 2) { printf("Error de CRC en los dos bloques\n"); } else { bloque = 2; printf("Error de CRC: Intentando con el bloque 2....\n"); Dir_Bloque = MII_DATA_ADDR + sizeof(miis); wiiuse_read_data(wiimotes[wiimote_activo], (ubyte*) &miis, Dir_Bloque, sizeof(miis), handle_read); } } else { bloque = 1; printf("Test de CRC correcto \n"); } printf("Leyendo desde mando %i.\n", wiimote_activo); DisplayMii(miis.MiiArray[mii_activo]); break; } }
En el caso de que tengamos seleccionada la NAND llamamos directamente a la función DisplayMii con el Mii seleccionado, ya que no tenemos que hacer ninguna comprobación adicional.
Si el origen de datos es el wiimote, primero deberemos comprobar el CRC para provocar una nueva lectura en el caso de que el test nos dé fallo. En ese caso intentamos leer el segundo bloque y si vuelve a dar error de CRC mostramos el mensaje “Error de CRC en los dos bloques”.
Para comprobar el CRC usamos la siguiente función:
int crc (char* bytes, int size) { unsigned int crc = 0x0000; int byteIndex, bitIndex,counter; for (byteIndex = 0; byteIndex < size; byteIndex++) { for (bitIndex = 7; bitIndex >= 0; bitIndex--) { crc = (((crc << 1) | ((bytes[byteIndex] >> bitIndex) & 0x1)) ^ (((crc & 0x8000) != 0) ? 0x1021 : 0)); } } for (counter = 16; counter > 0; counter--) crc = ((crc << 1) ^ (((crc & 0x8000) != 0) ? 0x1021 : 0)); return (crc & 0xFFFF); }
Tampoco es mi intención agobiaros explicando en detalle el cálculo del CRC, pero si tenéis curiosidad por el tema podéis consultar este articulo.
Básicamente consiste en aplicar una operación lógica a nivel de bits de un valor llamado polinomio, que en nuestro caso es 0x1021, a todos las palabras del bloque a comprobar.
En el momento en que se almacena el bloque de datos, se guarda dicho valor en los dos últimos bytes del bloque y luego cuando lo leemos lo volvemos a calcular y el resultado debe ser el mismo que se obtuvo en el momento de la escritura. Si no es así es que alguno de los valores del bloque ha sufrido algún cambio.
Vamos ya a ver la última de las funciones del tutorial, la función DisplayMii().
// Muestra por pantalla los datos del mii void DisplayMii(MII_DATA_STRUCT mii){ if (mii.isGirl) { printf("Chica \n"); } else {printf("Chico \n");} printf("Altura %i \n", mii.height); printf("Peso %i \n", mii.weight); printf("Nombre: "); printUnicode(&mii.name[0], MII_NAME_LENGTH); printf("Creador: "); printUnicode(&mii.creatorName[0], MII_CREATOR_NAME_LENGTH); if (mii.isFavorite) printf("Este Mii es un favorito\n"); if (mii.month>0 && mii.day>0) printf("Nacimiento: %i/%i\n", mii.month, mii.day); if (mii.downloaded) printf("Este Mii ha sido descargado\n"); printf("Color favorito: %i\n", mii.favColor); printf("Forma de cara %i, color %i, 'feature' %i.\n", mii.faceShape, mii.skinColor, mii.facialFeature); if (mii.hairPart) printf("Tipo de pelo %i, color %i, con melena.\n", mii.hairType, mii.hairColor); else printf("Tipo de pelo %i, color %i, normal.\n", mii.hairType, mii.hairColor); printf("Cejas tipo %i, color %i, angulo %i, tamano %i, alto %i, con espaciado %i.\n", mii.eyebrowType, mii.eyebrowColor, mii.eyebrowRotation, mii.eyebrowSize, mii.eyebrowVertPos, mii.eyebrowHorizSpacing); printf("Ojos tipo %i, color %i, angulo %i, tamano %i, alto %i, con espaciado %i.\n", mii.eyeType, mii.eyeColor, mii.eyeRotation, mii.eyeSize, mii.eyeVertPos, mii.eyeHorizSpacing); printf("Nariz tipo %i, tamano %i, y alto %i.\n", mii.noseType, mii.noseSize, mii.noseVertPos); printf("Labios tipo %i, color %i, tamano %i, y alto %i.\n", mii.lipType, mii.lipColor, mii.lipSize, mii.lipVertPos); if(mii.glassesType>0) printf("Lleva gafas de color %i, tipo %i, %i tamano y alto %i.\n", mii.glassesType, mii.glassesColor, mii.glassesSize, mii.glassesVertPos); if(mii.mustacheType>0) printf("Lleva bigote tipo %i, tamano %i, alto %i y color %i.\n", mii.mustacheType, mii.mustacheSize, mii.mustacheVertPos, mii.facialHairColor); if(mii.beardType>0) printf("Lleva barba tipo %i, color %i.\n", mii.beardType, mii.facialHairColor); if (mii.moleOn) printf("Tiene un lunar en la posicion %i, %i y su tamano es %i.\n", mii.moleHorizPos, mii.moleVertPos, mii.moleSize); }
Esta función es de lo más simple, y lo único que hace es mostrar los valores que previamente han sido leídos.
Sustituyendo esta función por otra que hiciera un render de los Miis podríamos tener un resultado gráfico, pero para ello antes habría que conocer más en detalle cómo funcionan las GX, algo que queda fuera del alcance del presente tutorial.
Adicionalmente, si os atrevéis a escribir en la memoria del mando, sería sencillo hacerlo utilizando la función de wiiuse_write_data que tiene una sintaxis muy similar a wiiuse_read_data.
int wiiuse_write_data(struct wiimote_t *wm,uint addr,ubyte *data,ubyte len,cmd_blk_cb cb)
Únicamente hay que tener en cuenta que sólo podemos escribir 16 bytes de una sola vez, por lo que habrá que hacer múltiples escrituras para escribir los 752 bytes completos.
En cuanto a la escritura en la NAND, simplemente debería de funcionar escribir en el mismo archivo con la función estándar fwrite, abriendo el archivo de la misma manera que lo hemos hecho para la lectura de datos. Aunque os he de decir que tengáis mucho cuidado con esto porque si no sabéis lo que hacéis podéis provocar un Brick.
Comentarios
wow
esto es un gran paso, la verdad no entendi nada, pero la cosa s que si se puede manejar asi con mi imaginense e el futuro, las proximas consolas de nintendo, imaginate que en un mi inyectas el hack mii installer y el instalador de homebrew chanel solo lo pasas al canal mii por mii installe, luego al mando de wiii, y asi poderlo mandar a otras personas las cuales no tienen homebrew y al abrir el mii te cargue el hack mii installer, seria un gran avamce.
Excelentísima información, a
Excelentísima información, a mi no me sirve, pero agradezco el inmenso esfuerzo.
duda??
se pueden editar mis fuera de la wii??
tambien se podria sacar un pokemon..del juegos de pokemon rumble..devido ake esos pokemon igual se pueden guardar en el wimote.. esto es muy interesante,,exelente tuto
Es muy posible que si se
Es muy posible que si se puedan editar los stats de los pokemons incluso sus movimientos ya que cuando transfieres uno a tu control su proposito es que lo pases a otro wii que tenga el pokemon rumble asi que talvez con un poco de estudio alguien sea capaz de transferirlos al pc por medio de la tecnologia bluetooth la cual facilita mucho las cosas si estas pensado en algo asi seri interesante :D
▄▲►˝Άv69"►▲▄
Me lo anoto para el
Me lo anoto para el futuro.
De momento tengo un par de juegos para desarrollar, pero si encuentro un hueco en el futuro puede que lo investigue.
Curso aplicado de GRRLIB - Parte 1 - Parte 2 - Parte 3 - Parte 4 - Parte 5 - Parte 6 - Parte 7 - Parte 8 - Parte 9 - Parte 10 - Parte 11
Profundizando en los mandos de la Wii - Parte 1 - Parte 2 - Parte 3 - Parte 4 (Balanceboard) - Parte 5 (Miis)
Homebrew - WiiTriis - LifemiiWii
Claro que se pueden editar.
Claro que se pueden editar. Para ello habría extraer los Wiis salvando el array de Miis a un archivo en la SD y creae un programa en el PC que utilizara la información de ese archivo para modificar el Mii.
Ya existe una aplicaciones para extraer, editar y volcar la información de los Miis, aunque si lo que quieres es hacer tu propio programa el camino es el que he indicado en el parrafo anterior.
En cuanto lo de sacar un pokemon o cualquier otro personaje de otro juego, por supuesto que es posible pero habría que investigarlo para cada juego en concreto, porque cada programador utiliza su formato de datos.
Curso aplicado de GRRLIB - Parte 1 - Parte 2 - Parte 3 - Parte 4 - Parte 5 - Parte 6 - Parte 7 - Parte 8 - Parte 9 - Parte 10 - Parte 11
Profundizando en los mandos de la Wii - Parte 1 - Parte 2 - Parte 3 - Parte 4 (Balanceboard) - Parte 5 (Miis)
Homebrew - WiiTriis - LifemiiWii
Este???
http://wii.scenebeta.com/noticia/my-avatar-editor
Paz!!
gracias como siempre por tus
gracias como siempre por tus tutos, le tirare un ojo después con mas calma
Gracias a vosotros por
Gracias a vosotros por seguirlos.
si tenéis cualquier duda decídmelo e intentaré resolverla.