Come molti di voi già sapranno, c'è un bug nell'Homebrew Channel che manda in crash le applicazioni homebrew qualora la Wii non sia connessa in rete e l'HBC sia configurato per caricare le applicazioni senza fare il reload dell'IOS, che per altro è l'unico metodo noto per avere i diritti di accesso AHBPROT.
Questo problema colpisce tutte le applicazioni caricate tramite HBC 1.0.7 o successivo.
Infatti l'HBC lancia una chiamata asincrona alla Wii per rilevare la rete.
Tipicamente l'IOS58 impiega molto tempo a rispondere qualora la connessione non venga trovata.
Il problema è che l'HBC non termina lato IOS la comunicazione con l'hardware di rete, in altre parole non cancella la chiamata asincrona prima di lanciare una applicazione homebrew.
Ne segue che la risposta IPC alla chiamata asincrona arriva quando la applicazione sta già girando, causando l'indesiderato crash.
Le soluzioni tampone note ad oggi
Ci sono 2 workaround noti che permettono di tamponare il problema ma entrambe presentano degli inconvenienti non indifferenti:
- Il primo consiste nel forzare l'HBC a ricaricare l'IOS prima di avviare l'applicazione, cosa che di per sè chiude automaticamente qualunque chiamata pendente prevenendo il crash dell'applicazione.
Tuttavia questa soluzione ci riporta indietro di almeno un anno, facendo perdere all'applicazione i diritti di accesso AHBPROT. - Una soluzione alternativa è quella di aspettare circa 1 minuto nel menu dell'HBC con la speranza che nel frattempo la chiamata asincrona termini di sua spontanea volontà.
Tuttavia l'esperienza insegna che questa soluzione non funziona sempre per tutte le Wii in circolazione.
Alla ricerca di una soluzione definitiva
Questo vecchio Registrati o effettua il Login per visualizzare il link!. è stato davvero illuminante e mi ha dato sufficienti informazioni per iniziare a investigare.
La syscall 0x54, anche nota come set_ahbprot, permette di abilitare/disabilitare l'accesso AHBPROT. Si veda Registrati o effettua il Login per visualizzare il link!.
Disassemblando e ispezionando il Kernel (che contiene i moduli FFS, ES e IOSP) si scopre che la syscall 0x54 viene chiamata solo 2 volte ed entrambe le chiamate provengono dalla stessa funzione, la quale a sua volta è chiamata dallo ioctl LAUNCH_TITLE.
Questa funzione verifica i diritti di accesso nel TMD del titolo da lanciare, cioè i 4 byte con offset 0x1D8. Si veda Registrati o effettua il Login per visualizzare il link!.
Ecco una rapida traduzione in C della parte saliente di questa funzione:
- Codice: Seleziona tutto
void check_access_rights(void *tmd)
{
...
s32 access_rights = *(s32 *)(tmd+0x1D8);
/* Shift bit 0 to the sign bit and check it */
if ((access_rights << 31) < 0) {
/* Bit 0 is ON so enable AHBPROT */
set_ahbprot(1);
}
else {
/* Bit 0 is OFF so disable AHBPROT */
set_ahbprot(0);
}
...
}
L'idea dietro al fix è quella di patchare questa funzione in memoria in modo da emulare la presenza dei diritti di accesso AHBPROT nel TMD senza andare di fatto a modificare il TMD stesso, cosa che naturalmente invaliderebbe la firma.
Dopo di ciò è sufficiente ricaricare l'IOS per terminare automaticamente la chiamata asincrona citata in precedenza.
Naturalmente per patchare l'IOS in esecuzione occorre avere i diritti AHBPROT, ma questo non è un problema poichè ce li fornisce l'HBC.
Capito questo il gioco è fatto. Dobbiamo abilitare l'AHBPROT chiamando la syscall set_ahbprot(1) indipendentemente dal bit 0 nel campo dei diritti di accesso del TMD.
In pratica si vuole che il codice precedente diventi qualcosa del genere:
- Codice: Seleziona tutto
void check_access_rights(void *tmd)
{
...
s32 access_rights = *(s32 *)(tmd+0x1D8);
/* Shift bit 0 to the sign bit and check it */
if ((access_rights << 31) < 0) {
/* Bit 0 is ON so enable AHBPROT */
set_ahbprot(1);
}
else {
/* Bit 0 is OFF but we still enable AHBPROT */
set_ahbprot(1);
}
...
}
E finalmente il fix
Passiamo finalmente al codice per cercare il pattern binario, patchare il giusto byte in memoria e poi ricaricare l'IOS.
Questo è tutto ciò che ci serve per fixare il problema della connessione.
- Codice: Seleziona tutto
#define HAVE_AHBPROT ((*(vu32*)0xcd800064 == 0xFFFFFFFF) ? 1 : 0)
#define MEM_REG_BASE 0xd8b4000
#define MEM_PROT (MEM_REG_BASE + 0x20a)
static void disable_memory_protection() {
write32(MEM_PROT, read32(MEM_PROT) & 0x0000FFFF);
}
static u32 apply_patch(char *name, const u8 *pattern, u32 pattern_size, const u8 *patch, u32 patch_size, u32 patch_offset) {
u8 *ptr_start = (u8*)*((u32*)0x80003134), *ptr_end = (u8*)0x94000000;
u32 found = 0;
u8 *location = NULL;
while (ptr_start < (ptr_end - patch_size)) {
if (!memcmp(ptr_start, pattern, pattern_size)) {
found++;
location = ptr_start + patch_offset;
u8 *start = location;
u32 i;
for (i = 0; i < patch_size; i++) {
*location++ = patch[i];
}
DCFlushRange((u8 *)(((u32)start) >> 5 << 5), (patch_size >> 5 << 5) + 64);
ICInvalidateRange((u8 *)(((u32)start) >> 5 << 5), (patch_size >> 5 << 5) + 64);
}
ptr_start++;
}
return found;
}
const u8 es_set_ahbprot_pattern[] = { 0x68, 0x5B, 0x22, 0xEC, 0x00, 0x52, 0x18, 0x9B, 0x68, 0x1B, 0x46, 0x98, 0x07, 0xDB };
const u8 es_set_ahbprot_patch[] = { 0x01 };
u32 IOSPATCH_AHBPROT() {
if (HAVE_AHBPROT) {
disable_memory_protection();
return apply_patch("es_set_ahbprot", es_set_ahbprot_pattern, sizeof(es_set_ahbprot_pattern), es_set_ahbprot_patch, sizeof(es_set_ahbprot_patch), 25);
}
return 0;
}
int main(int argc, char* argv[])
{
int ret;
/* Enable AHBPROT on title launch */
ret = IOSPATCH_AHBPROT();
if (ret) {
/* Reload current IOS, typically IOS58 */
IOS_ReloadIOS(IOS_GetVersion());
}
else {
/*
* Fatal error!!!
* Unable to patch the running IOS.
* The application has been likely launched without AHBPROT access rights.
*/
}
/*
* Put your code here
*/
}
Note
- Questo approccio è stato pesantemente testato su una versione dell'IOS236 Installer v5 che ho appositamente modificato come descritto.
Questa nuova versione non ha mai fallito una volta nonostante tutti i mie sforzi.
E tenete presente che quando lancio l'IOS236 Installer v5 ORIGINALE la mia Wii va praticamente sempre in code dump prima di riuscire a terminare la procedura. - La funzione apply_patch qui sopra ha la pretesa di essere generica, quindi non è ottimizzata ed una bella fetta della memoria viene scandagliata alla ricerca del pattern binario.
Potreste voler restringere questo intervallo di memoria, tuttavia dai test risulta che la funzione è veloce quanto basta per evitare problemi, parliamo comunque di millesimi di secondo. - Con lo stesso approccio descritto in questo topic siamo in grado di patchare il bit 1 del campo per i diritti di accesso del TMD in modo da abilitare la modalità DVD video.
Se siete interessati a questa patch fatemelo sapere e la rilascerò.