Anuncios Google

Profundizando en los mandos de la Wii - Parte 5 (Miis)

Tutoriales Avanzados Homebrewes

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.

Advertencia: Ni el autor ni el grupo de Scenebeta se hacen responsables de cualquier daño que este tutorial pueda ocasionar a tu Wii o sus derivados (Como los mandos). Tu eres el completo responsable de lo que haces.


Fuente y biblioteca

Fuentes del curso Enlace externo

Biblioteca IFSF Enlace externo


¿¿Un Mii en mi mando?? Como copiar un Mii al mando.

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:

  • Seleccionamos el Canal Mii y lo arrancamos.

  • Una vez en la plaza, en la parte derecha de la pantalla tenemos tres iconos. Hay que seleccionar el de en medio “Transferir Mii” que muestra un wiimote.

  • Después nos aparecerá una pantalla para seleccionar el wiimote:

  • Si en el wiimote no hemos copiado nunca ningún Mii nos preguntará si queremos formatear la memoria. Hay que responder Sí.
  • Seguidamente deberemos arrastrar el Mii deseado, pulsando A+B, a uno de los 10 slots disponibles en el mando.

  • Cuando terminemos de arrastrar Miis guardamos y salimos y ya tenemos los Miis preparados en el mando para poder continuar con el tutorial.


Echando un ojo dentro del mando - Formato de datos.

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.


Formato del bloque de Miis.

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.


Formato de los Miis

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:

  • Las cadenas son guardadas en formato Unicode (UTF-16), big endian, lo cual nos obliga a hacer una función específica para mostrarlos en pantalla.
  • Seleccionar valores faciales o de colores mayores que los límites hace que no se muestren cuando intentamos recuperarlos del mando.
  • Peso y altura están limitados a 0x7F. Cualquier valor superior tendrá exactamente el mismo efecto que 0x7F.
  • El ID es un identificador único para el Mii pero también permite asignar ese Mii como especial (pantalones dorados). Podéis consultar un artículo sobre los valores posibles  aqui.


Formato de los Mii en la NAND de la Wii.

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:



Utilizando Wiiuse

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:

  • Inicializamos la biblioteca con ISFS_Initialize
  • Montamos el sistema de archivos con ISFS_Mount
  • Leemos los datos llamando a la función readmii
  • Desmontamos el sistema de archivos.
  • Volcamos los Miis leídos en un array que nos permita gestionarlos llamando a la función loadMiis.

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:

  • Abrimos el archivo que contiene los miis con fopen.
  • Reservamos el tamaño necesario de memoria para leer los datos con malloc, siendo este tamaño igual al número máximo de miis(100) por el tamaño de cada mii + la cabecera.
  • Leemos los datos con fread y cerramos el archivo con fclose.
  • Finalmente devolvemos un puntero a la información leída.

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:

  • Pulsando A mostraremos la información del Mii actual.
  • Con arriba y abajo cambiaremos el origen de datos entre la memoria Nand y los Wiimotes.
  • Con izquierda y derecha cambiaremos el mando seleccionado.
  • Con + y – nos desplazaremos a través de los Miis.

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:

  • wm: aquí debemos pasar un puntero al elemento correspondiente a nuestro mando del array que nos devolvió wiiuse_init. Es decir en el caso de que esté seleccionado el mando 1 deberemos pasar el elemento número 0 de dicho array, ya que el array comienza en 0.
  • buffer: hay que pasar un puntero a la zona de memoria donde queremos recibir los datos. En nuestro caso pasamos un puntero a la estructura de datos que tenemos reservada al efecto “miis”
  • Addr: hay que pasar la dirección de lectura. En nuestro caso tenemos dos direcciones de lectura posibles: 0x0FCA dirección donde el wiimote almacena la primera copia de los Miis; 0x0FCA + 752 que es la dirección donde se almacena la segunda copia que tendremos que leer en el caso de que el CRC nos dé error.
  • Len: Longitud de los datos a leer. En nuestro caso 752.
  • Cb: Función de callback de lectura. Esta función será llamada automáticamente cuando se termine la lectura de los datos y los tengamos disponibles para mostrar. En nuestro caso pasamos la dirección de la función handle_read.

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.

4.34091
Tu voto: Ninguno Votos totales: 4.3 (44 votos)

Anuncios Google

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de Kevin-GT

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.

Imagen de Enric88

Excelentísima información, a

Excelentísima información, a mi no me sirve, pero agradezco el inmenso esfuerzo.

Imagen de kom.12

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"►▲▄

Imagen de wilco2009

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

Imagen de wilco2009

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

Imagen de adriancid

gracias como siempre por tus

gracias como siempre por tus tutos, le tirare un ojo después con mas calma

Imagen de wilco2009

Gracias a vosotros por

Gracias a vosotros por seguirlos.

si tenéis cualquier duda decídmelo e intentaré resolverla.

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.