|
|
Tutorial avanzato #2 - Aggiunte alla Shiny Hack
This tutorial was translated by the User HackMew. The original version can
be found in the tutorial-section.
If you want to provide your support to this website by translating this tutorial to another language,
please contact me. (Details can be found in the imprint)
Thanks!
Ben ritrovati, hacker! Con il rilascio della shiny hack,
creata originariamente per la stupenda hack Shiny Gold opera di zel, il
mio laboratorio ha ricevuto più attenzione. Ho deciso quindi di
continuare con le lezioni per utenti esperti.
Comunque, tornando all´argomento...
Sebbene la shiny hack non fosse affatto male, presentava
due difetti principali. (Ringrazio le persone che mi hanno informato del
secondo, era una piccola svista. Non avevo tenuto conto di una
possibilità):
- - lo shiny generato aveva sempre la stessa natura e lo stesso sesso
- - se perdevi contro il Gyarados shiny (o qualsiasi altro shiny), lo shiny flag
non veniva azzerato e ogni incontro successivo sarebbe stato shiny
Oggi sistemeremo le cose, lavorando ancora una volta con
la versione americana di Pokémon Rosso Fuoco. Con un po´ di fortuna non
avrai dimenticato nulla nel frattempo. ;-)
Primo problema: la casualità
Ricordiamo l´intera condizione shiny:
((PID XOR OTrainerIDs) >> 0x10) XOR ((PID XOR OTrainerIDs) AND 0xFFFF) < 8
Se questa linea non ti dice nulla, leggiti il Tutorial
avanzato #1.
Ti ricordi del fatto che (PID XOR OTrainerIDs) >> 0x10) e (PID XOR OTrainerIDs) AND 0xFFFF)
non possono differire molto dal numero otto? Seguiamo questa riflessione;
se introduciamo la casualità questo qui sotto sarebbe il risultato:
((Rand XOR OTrainerIDs) >> 0x10) XOR ((Rand XOR OTrainerIDs) AND 0xFFFF) < 8
Ora la parte complicata: in quali casi otteniamo uno shiny?
((OTrainerIDs XOR OTrainerIDs) >> 0x10) XOR ((OTrainerIDs XOR OTrainerIDs) AND 0xFFFF) < 8
(0x0 >> 0x10) XOR (0x0 AND 0xFFFF) < 0x8
0 XOR 0 < 0x8
0 < 8
La nostra vecchia soluzione. Restituisce 0. Ora,
se i 2 byte più significativi e quelli meno significativi non differiscono
molto da otto, abbiamo uno shiny. Così possiamo dire:
(((OTrainerIDs XOR (Rand Or (Rand << 10))) XOR OTrainerIDs) >> 0x10) XOR ((OTrainerIDs XOR (Rand Or (Rand << 10))) AND 0xFFFF) < 8
(((0x65A9DC70 XOR (Rand Or (Rand << 10))) XOR 0x65A9DC70) >> 0x10) XOR (((0x65A9DC70 XOR (Rand Or (Rand << 10))) XOR 0x65A9DC70) AND 0xFFFF) < 8
(((0x65A9DC70 XOR (0xD523 Or (0xD523 << 10))) XOR 0x65A9DC70) >> 0x10) XOR (((0x65A9DC70 XOR (0xD523 Or (0xD523 << 10))) XOR 0x65A9DC70) AND 0xFFFF) < 8
(((0x65A9DC70 XOR 0xD523D523) XOR 0x65A9DC70) >> 0x10) XOR (((0x65A9DC70 XOR 0xD523D523) XOR 0x65A9DC70) AND 0xFFFF) < 8
(0xD523D523 >> 0x10) XOR (0xD523D523 AND 0xFFFF) < 8
0xD523 XOR 0xD523 < 8
0 < 8
Ricorda che ho sostituito il PID con (OTrainerIDs XOR (Rand Or (Rand << 10))). Rand#
un denominatore a 16bit non segnati. (Io ho scelto 0xD523 come valore).
Perciò, impostando la Xkey come Rand# OR (Rand# << 10) come nell´esempio
sopra riportato, ciò che viene restituito è 0. Il successivo XOR OTrainerIDs
è stato introdotto per sbarazzarsi del primo OTrainerIDs.
Questa è la teoria. E adesso: come può esistere un numero casuale
all´interno del gioco? La risposta è semplice - non ce ne sono. O
meglio, non esistono numeri casuali ´veri´. Per capire meglio il
concetto, daremo un´occhiata più da vicino al RNG (Random Numbers
Generator, Generatore di Numeri Casuali). È sempre posizionato all´indirizzo 0x03005000.
Apri quindi il VBA ed il visualizzatore di memoria (Memory viewer) e ti
accorgerai di 4 byte che cambiano rapidamente in quella posizione.
Perché quindi non si tratta di ´veri´ numeri casuali? Andiamo a vedere la
routine che ne è responsabile.
Apri VBA-SDL-H, carica il gioco e premi F11 per fermarne
l´esecuzione. Posiziona un bpw su 03005000 per 4 byte.
Digita c per continuare. Il gioco dovrebbe fermarsi immediatamente. Nel
mio caso:
Breakpoint (on write) address 03005000 old:77a8a7b8 new:c639d9cb
R00=c639d9cb R04=04000006 R08=00000000 R12=00000273
R01=00006073 R05=030030f0 R09=00000000 R13=03007e14
R02=03005000 R06=030030e4 R10=00000000 R14=0800078d
R03=00000000 R07=030030f0 R11=00000000 R15=08044ed8
CPSR=8000003f (N.....T Mode: 1f)
08044ed6 0c00 lsr r0, r0, #0x10
Hai notato la mia espressione a 4 byte? Tu dovresti avere
un altro valore visto che ci stiamo occupando di numeri ´casuali´. :-P
Esploriamo l´intera routine.
08044ec8 4a04 ldr r2, [$08044edc] (=$03005000) // carica indirizzo
08044eca 6811 ldr r1, [r2, #0x0] // carica *indirizzo (valore 0x03005000)
08044ecc 4804 ldr r0, [$08044ee0] (=$41c64e6d) // carica l´RNG polinomiale
08044ece 4348 mul r0, r1 // nuovo valore = vecchio valore * 0x41c64e6d
08044ed0 4904 ldr r1, [$08044ee4] (=$00006073) // carica un altro valore
08044ed2 1840 add r0, r0, r1 // sommalo con il nostro numero ´casuale´
08044ed4 6010 str r0, [r2, #0x0] // salva nuovamente il numero ´casuale´ (32 bit)
08044ed6 0c00 lsr r0, r0, #0x10 // restituisci solo i 16bit più significativi
08044ed8 4770 bx lr // termina la funzione
Il nostro numero casuale dipende solamente da alcune
moltiplicazioni fra la RAM e il tempo. Quindi non si tratta di un vero
numero casuale, ma vi è piuttosto vicino. :-P
Di gran lunga più importante è la funzione stessa. Se chiami 0x08044ec8 da
qualsiasi punto all´interno della ROM, otterrai un numero casuale
depositato in r0. Con la conoscenza di ciò andremo a estendere la nostra
routine ´shiny check´ che abbiamo estratto nel primo tutorial. La
mia è localizzata all´offset 0x71b700 nello spazio libero della ROM.
0871b700 bc02 pop {r1} // ottieni il valore salvato
0871b702 b524 push {lr, r2, r5} // metti nello stack l´indirizzo di ritorno, salva
0871b704 bc02 pop {r1} // ricaricalo su r1
0871b706 3108 add r1, #4 // aggiusta l´indirizzo di ritorno di 8. (per saltare
l´indirizzo-bersaglio)
0871b708 b402 push {r1} // salva di nuovo l´indirizzo di ritorno
0871b70a 4812 ldr r0, [$0871b754] (=$08044ec8)
0871b70c f000 bl $0871b74c
0871b70e f824 blh $0048 // continua l´esecuzione da r0, salva l´indirizzo di
ritorno in lr
0871b710 0405 lsl r5, r0, #0x10 // shifta a sinistra il rnd
0871b712 1945 add r5, r5, r0 // raddoppia la semiparola ad una parola
0871b714 490e ldr r1, [$0871b750] (=$02022000) // carica la posizione del flag
0871b716 6809 ldr r1, [r1, #0x0] // carica il valore @ 02022000 su r1
0871b718 2901 cmp r1, #0x1
0871b71a d00b beq $0871b734 // è uno shiny? allora salta al gestore shiny
0871b71c 7821 ldrb r1, [r4, #0x0] // carica il @ r4 (=03007cf8) su r1
0871b71e 7860 ldrb r0, [r4, #0x1] // carica il byte @ r4+1 (=03007cf9) su r0
0871b720 0200 lsl r0, r0, #0x08 // il byte in r0 è shiftato a sinis. di 1 byte (=8bit)
0871b722 1809 add r1, r1, r0 // r1 e r0 sono sommati. otteniamo la semiparola r0+r1
0871b724 78a0 ldrb r0, [r4, #0x2] // carica il byte @ r4+2 (=03007cfa) su r0
0871b726 0400 lsl r0, r0, #0x10 // r0 è shiftato nuovamente a sinistra di 2 byte
0871b728 1809 add r1, r1, r0 // r0 è sommato a r1
0871b72a 78e0 ldrb r0, [r4, #0x3] // carica il byte @ r4+3 (=03007cfb) su r0
0871b72c 0600 lsl r0, r0, #0x18 // r0 è shiftato a sinistra di 3 byte
0871b72e 1809 add r1, r1, r0 // e sommato con r1
0871b730 6079 str r1, [r7, #0x4] // infine l´OTrainerIDs è salvato su r7+4
(=0202402c)
0871b732 e00c b $0871b74e // salta le linee seguenti. sono usate per gli shiny
0871b734 7821 ldrb r1, [r4, #0x0] // carica il byte @ r4 (=03007cf8) su r1
0871b736 7860 ldrb r0, [r4, #0x1] // carica il byte @ r4+1 (=03007cf9) su r0
0871b738 0200 lsl r0, r0, #0x08 // il byte in r0 è shiftato a sinis. di 1 byte (=8bit)
0871b73a 1809 add r1, r1, r0 // r1 e r0 sono sommati. otteniamo la semiparola r0+r1
0871b73c 78a0 ldrb r0, [r4, #0x2] // carica il byte @ r4+2 (=03007cfa) su r0
0871b73e 0400 lsl r0, r0, #0x10 // r0 è shiftato nuovamente a sinistra di 2 byte
0871b740 1809 add r1, r1, r0 // r0 è sommato a r1
0871b742 78e0 ldrb r0, [r4, #0x3] // carica il byte @ r4+3 (=03007cfb) su r0
0871b744 0600 lsl r0, r0, #0x18 // r0 è shiftato a sinistra di 3 byte
0871b746 1809 add r1, r1, r0 // e sommato con r1
0871b748 6079 str r1, [r7, #0x4] // infine l´OTrainerIDs è salvato su
r7+4 (=0202402c)
0871b74a 4069 eor r1, r5 // il PID è impostato come OTRainerIDs XOR rnd
0871b74c 6039 str r1, [r7, #0x0] // e qui c´è la nostra patch. il PID è sovrascritto
dall´OTrainerIDs
0871b74e bd24 pop {pc, r5, r2} // ritorna ed esci dalla sub, carica i valori salvati
0871b750 0020 // data1
0871b752 0202 // data2
0871b754 4ec8 // data3
0871b756 0804 // data4
0871b758 4787 bx r0 // vai a r0
Ecco tutto. Abbiamo introdotto la casualità. D´ora
in poi gli incontri degli shiny varieranno e non saranno più fissi.
Non inserire ancora la routine nella tua ROM dato
che non abbiamo ancora finito. Abbiamo da risolvere ancora un
problema: se perdi contro un Pokémon shiny, qualunque altro Pokémon
risulta poi shiny.
Un´idea semplice: diciamo che la routine resetta lo
shiny flag a 0, dopo essere stato letto. Quindi estenderemo
nuovamente la routine.
Secondo problema: l´infinità di shiny
0871b700 bc02 pop {r1} // ottieni il valore salvato
0871b702 b52d push {lr, r0, r2, r3, r5} // metti nello stack l´indirizzo di ritorno, salva
0871b704 9904 ldr r1, [sp, #0xc] // ricaricalo su r1
0871b706 3108 add r1, #4 // aggiusta l´indirizzo di ritorno di 8. (per saltare
l´indirizzo-bersaglio)
0871b708 9104 sdr r1, [sp, #0xc] // salva di nuovo l´indirizzo di ritorno
0871b70a 4813 ldr r0, [$0871b758] (=$08044ec9)
0871b70c f000 bl $0871b75c
0871b70e f826 blh $004c // continua l´esecuzione da r0, salva l´indirizzo di
ritorno in lr
0871b710 0405 lsl r5, r0, #0x10 // shifta a sinistra il rnd
0871b712 1945 add r5, r5, r0 // raddoppia la semiparola ad una parola
0871b714 4a0f ldr r2, [$0871b754] (=$02022000) // carica la posizione del flag
0871b716 6811 ldr r1, [r2, #0x0] // carica il valore @ 02022000 su r1
0871b718 2300 mov r3, #0x00 // imposta r3 a 0
0871b71a 6013 str r3, [r2, #0x0] // azzera lo shiny flag
0871b71c 2901 cmp r1, #0x1
0871b71e d00b beq $0871b738 // è uno shiny? allora salta al gestore shiny
0871b720 7821 ldrb r1, [r4, #0x0] // carica il @ r4 (=03007cf8) su r1
0871b722 7860 ldrb r0, [r4, #0x1] // carica il byte @ r4+1 (=03007cf9) su r0
0871b724 0200 lsl r0, r0, #0x08 // il byte in r0 è shiftato a sinis. di 1 byte (=8bit)
0871b726 1809 add r1, r1, r0 // r1 e r0 sono sommati. otteniamo la semiparola r0+r1
0871b728 78a0 ldrb r0, [r4, #0x2] // carica il byte @ r4+2 (=03007cfa) su r0
0871b72a 0400 lsl r0, r0, #0x10 // r0 è shiftato nuovamente a sinistra di 2 byte
0871b72c 1809 add r1, r1, r0 // r0 è sommato a r1
0871b72e 78e0 ldrb r0, [r4, #0x3] // carica il byte @ r4+3 (=03007cfb) su r0
0871b730 0600 lsl r0, r0, #0x18 // r0 è shiftato a sinistra di 3 byte
0871b732 1809 add r1, r1, r0 // e sommato con r1
0871b734 6079 str r1, [r7, #0x4] // infine l´OTrainerIDs è salvato su r7+4
(=0202402c)
0871b736 e00c b $0871b752 // salta le linee seguenti. sono usate per gli shiny
0871b738 7821 ldrb r1, [r4, #0x0] // carica il byte @ r4 (=03007cf8) su r1
0871b73a 7860 ldrb r0, [r4, #0x1] // carica il byte @ r4+1 (=03007cf9) su r0
0871b73c 0200 lsl r0, r0, #0x08 // il byte in r0 è shiftato a sinis. di 1 byte (=8bit)
0871b73e 1809 add r1, r1, r0 // r1 e r0 sono sommati. otteniamo la semiparola r0+r1
0871b740 78a0 ldrb r0, [r4, #0x2] // carica il byte @ r4+2 (=03007cfa) su r0
0871b742 0400 lsl r0, r0, #0x10 // r0 è shiftato nuovamente a sinistra di 2 byte
0871b744 1809 add r1, r1, r0 // r0 è sommato a r1
0871b746 78e0 ldrb r0, [r4, #0x3] // carica il byte @ r4+3 (=03007cfb) su r0
0871b748 0600 lsl r0, r0, #0x18 // r0 è shiftato a sinistra di 3 byte
0871b74a 1809 add r1, r1, r0 // e sommato con r1
0871b74c 6079 str r1, [r7, #0x4] // infine l´OTrainerIDs è salvato su
r7+4 (=0202402c)
0871b74e 4069 eor r1, r5 // il PID è impostato come OTRainerIDs XOR rnd
0871b750 6039 str r1, [r7, #0x0] // e qui c´è la nostra patch. il PID è sovrascritto
dall´OTrainerIDs
0871b752 bd2d pop {pc, r0, r5, r3, r2} // ritorna ed esci dalla sub, carica i valori salvati
0871b754 2000 // data1
0871b756 0202 // data2
0871b758 4ec9 // data3
0871b75a 0804 // data4
0871b75c 4700 bx r0 // vai a r0
Fantastico. Questo risolve anche il secondo problema.
Abbiamo finito, spero che apprezzerai la
´nuova´ shiny-hack! |
|