Questo Forum utilizza i Cookies
Questo Forum fa uso di Cookies per memorizzare le informazioni di login se sei registrato o sulla tua ultima visita se non siete registrati. I Cookies sono piccoli documenti di testo memorizzati sul vostro computer; i Cookies impostati dal Forum possono essere utilizzati solo su questo sito e non costituiscono rischio per la sicurezza. I Cookies su questo forum servono anche a monitorare gli argomenti specifici che hai letto. Si prega di confermare se accettare o rifiutare i Cookies impostati.

Un cookie verrà memorizzato nel browser indipendentemente dalla scelta, per evitare che il Forum faccia nuovamente questa domanda. Sarete in grado di modificare le impostazioni dei cookies in qualsiasi momento tramite il link a piè di pagina

Rispondi 
[Nds] Ricerca routine NDS
Avon
The down is real
**
Offline

Messaggi: 60
Registrato: 08-09-2014
Reputazione: 2
Mi Piace Ricevuti: 89
Pok√©Money: 160.00‚āĪ
Messaggio: #1
Ricerca routine NDS
Hi,
Premessa doverosa: NON sono un hacker NDS, prima di qualche ora fa sapevo meno di 0 e mentre sto scrivendo questo post forse so 0; sto aprendo questo thread perchè sul gruppo telegram del forum oggi è stato chiesto se qualche hacker che se la cava nel binary hacking GBA potesse dare una mano a capire come dirottare routine nell'ambito NDS come è comune fare su GBA e caso vuole sia stato io a rispondere.
Chiedo quindi a te che stai leggendo di prendere tutto con almeno 4 grani di sale, perchè appunto fondmentalmente non so di cosa sto parlando, sto solo applicando principi di informatica generali ad una ROM NDS.

Detto questo, il post serve semplicemente a illustrare gli step che ho intrapreso oggi per trovare la routine che gestisce il countdown di passi nei giochi NDS, in particolare ho usato Platino perchè coincidentalmente in questo periodo sto facendo una nuzlocke su quel titolo.

Step 0: Necessario


Lista della spesa:
  • Rom Pok√©mon NDS (in questo esempio PKMN Platino EU versione 10)
  • Desmume
  • no$gba versione debugger (link)
  • IDA (opzionale, per controllare il nostro risultato)
  • Un editor esadecimale (suggerisco HxD)

Step 1: Ipotesi


Per poter cercare qualcosa bisogna avere almeno una vaga idea di cosa si stia cercando: nel caso di cercare routine aiuta enormemente avere una vaga idea (ipotesi) di come funzioni la stessa.
Per fare ipotesi sensate gli strumenti migliore che abbiamo sono l'esperienza ed un po' di logica: se dovessimo rifare noi adesso da 0 la feature che è implementata dalla routine che stiamo cercando, cosa faremmo?
Nel nostro caso specifico, sicuramente scriveremmo in RAM il numero di passi che mancano alla fine dell'effetto del repellente e ci allacceremmo alla routine di GF che gestisce la camminata del player per decrementare di 1 questo contatore ad ogni passo, verificare se abbia raggiunto lo 0 e, in caso, terminare l'effetto del repellente.
Questa è stata la mia ipotesi (aiutata molto anche dal fatto che avessi già fatto in passato la stessa identica ipotesi su un gioco di terza gen e si fosse rivelata veritiera, come dicevo l'esperienza decisamente aiuta).

Step 2: I dati


Come - spero - chiunque stia leggendo questo post sappia, il codice non è altro che un mezzo per trasformare i dati, e le routine non fanno eccezione: in questo caso i dati sono il numero di passi rimanenti (che d'ora in poi chiamerò "countdown" per comodità) e un qualcosa che dice al gioco di non far avvenire incontri selvatici, probabilmente qualcosa di simile ad una flag che viene letta anche dalla routine che decide se farci incontrare un pokémon o meno.
Il modo migliore per trovare il codice che agisce su determinati dati è proprio quello di partire da questi dati, quindi vediamo di capire un po' quali dati cercare in questo caso specifico.
Abbiamo detto che c'è un flag e un countdown: il flag ha solo due valori con tutta probabilità (0 ed 1), e questi due valori sono troppo troppo generici, la RAM è piena di 0 ed 1, quindi direi di cercare invece il countdown, che fa tutti i valori da 100 ad 1, che sono sicuramente meno comuni.

Step 3: Search for Cheat


Per trovare il countdown ci serviremo di uno strumento presente sul desmume (forse c'è anche su no$, non sono sicuro tho): il search for cheat.
Questo signore fa uno scan della RAM cercando un dato valore X che gli diamo, e salva poi tutti gli offset che in questo momento contengono il nostro valore X in una lista L.
La prossima volta che faremo una ricerca per un altro valore, tipo X', il search for cheat non farà uno scan di tutta la RAM, ma solo degli indirizzi che sono nella sua lista L e la aggiornerà con i valori che prima contenevano X, e ora contengono X'.
Possiamo ripetere questo step quante volte vogliamo con X'', X''', Xn finchè L non contiene un solo offset: quell'offset sarà quello che contiene l'informazione che ci serve.
Per fare un esempio pi√Ļ pratico, apriamo Platino con il Desmume, usiamo un repellente e andiamo su Tools->Cheats->Search; in questo men√Ļ scegliamo Size: 1 byte (stiamo cercando un valore >=100, un byte √® pi√Ļ che sufficiente), sign: unsigned ed "Exact value search", premiamo poi su "Search" e scriviamo il nostro valore (in decimale, mi racommando), che in questo caso sar√† "100", perch√® appena usato il repellente mancano ancora 100 passi allo scadere dell'effeto; premiamo "Search" e chiudiamo la finestra.
Tornati al gioco, facciamo un passo e ritorniamo sul Search for Cheat, ma questa volta inseriamo come valore "99"; ripetiamo questo passo continuando a decrementare il numero ogni volta che facciamo un passo finchè la finestra di Cheat Search non ci darà number of results: 1.
A questo punto abbiamo trovato l'offset che contiene il nostro valore di countdown, clicchiamo su "View" e dovremmo vederlo; se state giocando a Platino EU versione 10 questo valore sarà 0x022861EB.

Step 4: I breakpoint


Ora che sappiamo dove sta il dato che ci interessa, possiamo facilmente trovare la routine che lo modifica mediante l'utilizzo di breakpoint.
Un breakpoint è un punto del codice nel quale lo stesso deve fermare l'esecuzione, questo punto è definito dallo sviluppatore a fini di debug o, come in questo caso, reverse engineering.
In particolare ci sono 3 tipi fondamentali di breakpoint:
  1. Breakpoint del codice: diciamo al codice che quando arriva ad una certa istruzione, questo si deve fermare
  2. Breakpoint on read: diciamo al codice che quando legge un certo indirizzo, questo si deve fermare
  3. Breakpoint on write: diciamo al codice che quando scrive ad un certo indirizzo, questo si deve fermare
Noi ci serviremo del no$gba per mettere un breakpoint on read sull'indirizzo che abbiamo trovato precedentemente (potremmo usarne anche uno in write in questo caso e cambierebbe poco).
Apriamo quindi il nostro no$gba e apriamo la ROM...con delle opzioni di men√Ļ francamente raccapriccianti: File->Cartridge menu(FileName). Perch√® "Open ROM" come un comune cristiano ci faceva schifo.
A questo punto possiamo utilizzare di nuovo il nostro repellente e, andando su Debug->Define Break/Condition possiamo definire un breakpoint di read con una sintassi ancora peggiore di quella usata per aprire la ROM: [OFFSET]? (quindi, nel nostro caso [0x022861EB]? ). e premiamo su OK. A questo punto avremo definito il nostro breakpoint!
Andiamo su Run->Run per far ripartire l'emulazione, facciamo un passo e...nulla.
Qui ho imprecato un po' prima di provare a fare una cosa: andando su Window->Debug Window->Data la finestrella pi√Ļ in basso del debugger diventer√† una visualizzazione in tempo reale della RAM; andando poi su Search->Goto, move cursor to address nnnn e inserendo il nostro indirizzo, troveremo 00, ma se faremo dei passi, noteremo che il valore nella riga esattamente sotto continua a decrementare di uno. Si, tra no$gba e desmume c'√® una discrepanza di 0x10 byte, non ho idea del perch√® ma √® cos√¨, magari √® il mio desmume, boh.
Inserendo quindi il breakpoint in read come [0x022861FB]? e facendo un passo finalmente l'emulatore si stopperà all'esecuzione della routine che controlla quel valore, ovvero quella del repellente.
Sappiamo che quella routine sia quella corretta perchè:
  1. Nessun'altra legge quel valore (né avrebbe interesse nel farlo): se infatti anche facessimo Run di nuovo, l'esecuzione si arresterebbe sempre nello stesso punto
  2. Controllando da IDA o anche da no$gba il decompilato, esce fuori quella che sembra essere a tutti gli effetti la routine del repellente:
    Codice:
        PUSH            {R4,LR}
        MOVS            R4, R1     @dunno
        BL              0xFFF720F4 @dunno^2
        BL              0xFFF7228C @dunno
        LDRB            R1, [R0]   @r0 a questo punto dell'esecuzione contiene 0x022861FB
        CMP             R1, #0        
        BEQ             loc_18B4E0 @Se quel valore contiene 0, allora vai alla fine, perch√® vuole semplicemente dire che il player non ha in utilizzo un repellente
        SUBS            R1, R1, #1 @Altrimenti vai avanti e sottrai uno al numero di passi
        STRB            R1, [R0]   @Scrivi in memoria il valore aggiornato
        LDRB            R0, [R0]   @Ricarica il valore in r0 (a questo punto sarebbe la stessa cosa di MOV r0, r1
        CMP             R0, #0     @Se siamo arrivati a 0 vuol dire che il repellente era attivo ma ora ha finito l'effetto
        BNE             loc_18B4E0 @Se √® diverso da 0, vai alla fine (nota il BNE)
        MOVS            R1, #0x7F  @Questa parte di routine gestisce il fatto che sia finito l'effetto del repellente
        MOVS            R0, R4
        LSLS            R1, R1, #4
        MOVS            R2, #0
        BL              0xFFF83140
    loc_18B4E0:
        MOVS            R0, #1
        POP             {R4,PC}

Step 5: ROM e RAM


Ah, ma i pi√Ļ furbi di voi sanno che non abbiamo trovato l'indirizzo vero della routine: se fossimo su GBA saremmo gi√† in vacanza al Cairo, ma l'NDS ha un ultimo ostacolo: il codice viene caricato in RAM prima di essere eseguito, non viene eseguito dalla ROM, quindi a partire da quello in RAM dobbiamo trovare quello in ROM, come?
...
...
...
Non ne ho idea.
Quindi ho utilizzato un metodo molto molto molto molto molto molto molto molto molto molto molto molto molto molto molto molto molto molto molto moltopoco ortodosso.
Ecco, vedete la rappresentazione esadecimale delle istruzioni (sotto spoiler)?
Ecco, invertendo i byte a due a due (mettendoli in little endian) otterremo la rappresentazione byte per byte di quella routine e... Beh, cercando quella lunga stringa di byte √® praticamente impossibile ci siano pi√Ļ occorrenze quindi... si, ho semplicemente composto la routine in esadecimale, aperto un editor esadecimale e cercato lo stringone di byte.
In particolare per la routine evidenziata lo stringone esadecimale sarebbe:
Codice:
10 B5 0C 1C E6 F5 1C FE E6 F5 E6 FE 01 78 00 29 0C D0 49 1E 01 70 00 78 00 28 07 D1 7F 21 20 1C 09 01 00 22 F7 F5 32 FE 01 20 10 BD 00 20 10 BD
Cercando questa roba nell'editor esadecimale l'unico offset che corrisponde è 0x18B4B4, e quella è la nostra routine.

Step 6: To be continued


A questo punto abbiamo la routine, possiamo dirottarla, ma mi chiedo, come diavolo faremo a chiamare una routine nostra a partire da qua se il nostro codice realisticamente non sarà caricato in RAM?
Essendo appunto ignorante dell'hacking NDS non so (ancora) rispondere, magari esiste già un metodo testato e funzionante
(Questo messaggio è stato modificato l'ultima volta il: 03-12-2020 05:19 PM da Avon.)
03-12-2020 05:09 PM
Cerca Cita
 Mi piace ricevuti da: Bonnox , JackHack96 , bonzi , PacoScarso , IvanFGK , LD3005 , Boh , 80C , MegaTorterra
LD3005
Music Composer
****
Offline

Messaggi: 409
Registrato: 07-01-2015
Reputazione: 17
Mi Piace Ricevuti: 346
Pok√©Money: 546.50‚āĪ
Messaggio: #2
RE: Ricerca routine NDS
Ehi, innanzitutto un grandissimo GRAZIE per esserti preso la briga di fare quest'intera ricerca dopo un mio semplice spunto. Molte persone sono già a conoscenza del metodo [inteso come procedimento] ma non sono così aperte alla divulgazione.

Tuttavia ho ricostruito i tuoi stessi passi ma non sono giunto agli stessi risultati.
Anzi, se cerco il countdown dei passi ottengo un indirizzo di ARM9 diverso per ogni volta che resetto il gioco.
Stesso dicasi per HeartGold e SoulSilver.

Allocazione dinamica, sembrerebbe. Eppure ti sei 'allacciato' a questo indirizzo. Forse perché non importa dove sia il dato 'al momento' ma solo chi vi accede, giusto?


On a side note, il tuo metodo "poco ortodosso" per la ricerca del codice sinceramente √® lo stesso che avrei usato anch'io. Mi sembra il pi√Ļ immediato in assoluto [se non l'unico...? Non mi vengono in mente alternative cos√¨ semplici].

Ancora grazie per tutti gli sforzi. ‚̧
Spero di poter contare su di te per futuri approfondimenti sul tema.


"Without music, life would be a mistake."
[Immagine: baGChhF.png][Immagine: VjxMyjR.png]

Guida al Music Hacking NDS
[Immagine: Ci2zGTN.png]
03-12-2020 06:04 PM
Sito Web Cerca Cita
 Mi piace ricevuti da: Boh
Avon
The down is real
**
Offline

Messaggi: 60
Registrato: 08-09-2014
Reputazione: 2
Mi Piace Ricevuti: 89
Pok√©Money: 160.00‚āĪ
Messaggio: #3
RE: Ricerca routine NDS
Uhm, definisci 'reset del gioco' per favore(?)
Perchè avevo provato anche io a 'resettare', ma in realtà quello che ho fatto è stato resettare il gioco e rifare meccanicamente le stesse azioni di aprire la borsa* e usare il repellente (anche perchè per passare da desmume a no$ la ROM devi caricarla due volte).
Comunque si, non dovrebbe importare fintanto che la routine alla fine della fiera va a leggere lì.
Una cosa che si può fare in caso comunque è mettere comunque il breakpoint su questo dato che continua a cambiare per trovare il meccanismo di DMA e disabilitarlo come si fa solitamente in FR

*Che comunque aprire e chiudere la borsa almeno in terza gen mi pare innescasse il DMA
03-12-2020 06:13 PM
Cerca Cita
 Mi piace ricevuti da: Boh
LD3005
Music Composer
****
Offline

Messaggi: 409
Registrato: 07-01-2015
Reputazione: 17
Mi Piace Ricevuti: 346
Pok√©Money: 546.50‚āĪ
Messaggio: #4
RE: Ricerca routine NDS
(03-12-2020 06:13 PM)Avon Ha scritto:  Uhm, definisci 'reset del gioco' per favore(?)
Letteralmente intendevo un riavvio dell'emulatore con la stessa ROM

"Without music, life would be a mistake."
[Immagine: baGChhF.png][Immagine: VjxMyjR.png]

Guida al Music Hacking NDS
[Immagine: Ci2zGTN.png]
03-12-2020 06:18 PM
Sito Web Cerca Cita
Avon
The down is real
**
Offline

Messaggi: 60
Registrato: 08-09-2014
Reputazione: 2
Mi Piace Ricevuti: 89
Pok√©Money: 160.00‚āĪ
Messaggio: #5
RE: Ricerca routine NDS
Ok, ho provato con Platino US ed effettivamente l'indirizzo cambia di riavvio in riavvio, ma per ogni riavvio dell'NDS poi rimane costante, non come la DMA di 3a gen che cambia ogni volta che fai una di n azioni.
Ci sto guardando ma la tana del coniglio è profonda.
Quello che per ora ho notato è che comunque la routine non cambia posizione e che anche a seguito di riavvii gli offset mi sono parsi relativamente vicini.

Un paio di scoperte abbastanza importanti però le ho fatte nel cercare di capire come funzioni il caricamento del codice in RAM: la prima è che in fase di compilazione il compilatore sa già come verrà caricato in RAM il codice, la seconda è che il codice che viene caricato in RAM non è un dump 1 a 1 di quello che c'è nella ROM.

La cosa diventa evidente quando prendiamo in considerazione l'istruzione a 0x18B4BC, che su IDA è un BL 0xFFF7228C (dove ovviamente 0xFFF7228C è un indirizzo FUORI dalla ROM). Questo BL ha una rappresentazione esadecimale F5 E6 FE E6, che vuol dire: "salta all'indirizzo PC - 0x‭219430". Ora, visto che l'istruzione è collocata a 0x18B4BC, che è inferiore a 0x‭219430‬, giustamente a IDA viene uno schiopone e impazzisce.
Questa operazione una volta che viene caricata in memoria viene interpretata così:
[Immagine: SkQNgOt.png]

Cioè un BL che arriva a 0x0202D9CC partendo da 0x02246BFC... E sottrarre il primo al secondo restituisce 0x219230, che è esattamente il salto che è scritto nel codice (quindi in fase di compilazione si conosce già la distribuzione del codice in RAM) e che non è 1 a 1 rispetto a quanto presente nella ROM (l'indirizzo nella ROM della routine che viene raggiunta è 0x319CC, non esiste nemmeno una relazione tra le due distanze).

Le uniche cose che rimangono da capire sono:
  • Se a compile-time si conosca solo la relazione tra le varie parti del codice (quindi la routine X √® sempre collocata n byte dopo Y) oppure se si conosce la vera e propria locazione, questa verifica la farei cercando qualcosa che in 3a gen si farebbe con uno switch e vedere come √® stato implementato qui (visto che lo switch fondamentalmente una tabella con dentro gli offset i dove andare per ogni caso)
  • Se il codice in RAM cambi o se rimanga fisso
  • Eventualmente capire come caricare in RAM il proprio codice
'Na passeggiata, insomma.
03-12-2020 09:33 PM
Cerca Cita
 Mi piace ricevuti da: LD3005 , Bonnox , Boh
Bonnox
Signore del Tempo un po' imbranato
*****
Offline

Messaggi: 2,060
Registrato: 06-07-2014
Reputazione: 11
Mi Piace Ricevuti: 633
Pok√©Money: 2,329.75‚āĪ
Messaggio: #6
RE: Ricerca routine NDS
ciao, questo messaggio non ha la pretesa di insegnare a nessuno, anzi al contrario, dato che sto brancolando nel buio perch√® non ho voglia di imparare (principalmente gbatek), cosa che magari far√≤ con calma nelle prossime settimane (mi pare di capire che non ci sia fretta, no? abbiamo gi√† aspettato 10 anni... cosa fa un giorno in pi√Ļ)

vorrei riportare un sunto della conversazione che ho avuto ieri con LD su telegram.
Non √® tutta al completo, ma giusto i punti pi√Ļ importanti, e delle supposizioni che magari possono far accendere una lampadina nella testa di chi legge!


esistono dei plugin di IDA chiamati loader, ovvero dei programmini che prendono il controllo del file e dicono a IDA cosa fare, costruendo per conto loro il database con molte pi√Ļ informazioni. e sembra che essi mappino tutto il file arm9 nella ram, 1 a 1.

questi funzionano con TUTTE le rom, in quanto hanno tutte la stessa struttura, decretata dall'SDK proprietario.

estraendo l'arm9 dalla rom e aprendolo con ida abbiamo ricevuto risultati diversi. ma perchè? boh.

tuttavia con heart gold ricevevamo solo una manciata di funzioni, nella finestra a sinistra. salta fuori che da quel gioco in poi per qualche ragione l'arm9 è "compresso", e quindi il loader non funziona bene ed interpreta erroneamente ciò che trova.

poi ad un certo punto lui legge male una cosa che ho detto e questo gli dà una idea fenomenale:
ha aperto una rom con un file manager di rom,
estratto il file arm9, decomprimendololo,
ha rimesso il file arm9 decompresso nella rom
ha aperto IDA facendo fare al loader il suo lavoro...

e, in teoria... bingo! immediatamente avevamo centinaia di routine!

...poi mi è venuto in mente un pensiero: pur io non sapendo come funziona in dettaglio l'architettura del DS, che senso ha sprecare circa un quarto di RAM con il codice???


poi, dimenticandoci dei problemi pi√Ļ grossi, abbiamo visto che l'indirizzo in cui risiede il contatore dei passi del repellente √® dinamico... ma sembra che alla fine cada sempre in un pezzo lungo 256 byte. le ultime 2 cifre dell'indirizzo insomma.

poi abbiamo verificato che gli emulatori riescono ad aprire i giochi pokemon HGSS e seguenti con arm9 decompresso. quindi la compressione potrebbe essere una funzione dell'architettura, invece che del programma pokemon?


ora inizia il periodo di schizofrenia in cui si susseguono un sacco di cose sparate a caso in un breve lasso di tempo.

non sono riuscito a trovare i byte della routine di avon nel mio arm9. solo che lui ha usato desmume, non ha toccato il file.

mi pare di aver letto che la compressione del arm9 è una variante del LZ, che è lo stesso tipo che c'è su GBA

alcuni utenti, in siti random, hanno fatto delle guide su come ottenere degli effetti modificando l'arm9!!! ma non spiegano come li hanno trovati...


supposizione: e se il codice stesso avesse indirizzi dinamici?!?! invece che caricare 1 a 1 come dice ida, magari il codice "kernel" carica nuovi pezzi di codice, molto pi√Ļ piccoli del MB che pesa l'arm9, e ci "salta" dentro.

come mai la gente esperta che dice cosa modificare non menziona mai se l'arm9 è compresso o no? è forse una cosa data per scontata? che sia decompresso?


poi mi sono ricordato che avon aveva lamentato il fatto che gli indirizzi su nosgba non corrispondono a quelli di ida, e ho detto che nell'architettura il branch non contiene l'indirizzo completo, ma solo un campo, molto ampio, che dice quanto scostarsi rispetto alla posizione corrente. per questo forse gli indirizzi sono diversi. magari nella rom un BL ti dice "salta a 1000 bytes da qua", ma se lo metti in ram ti dirà... "salta a 1000 bytes da qua", ma l'emulatore te lo mostra diversamente!

ultima idea, prima di andare a dormire, anche dopo aver appreso da avon sul gruppo telegram che la rom viene letta in unit√† grandi 512 bytes: magari non viene tutta caricata in RAM, per√≤ mantiene gli indirizzi corrispondenti a 1 a 1. questo significa che comunque non si pu√≤ avere pi√Ļ di 4 mega di codice. per√≤ magari i settori che non sono in uso vengono deallocati e si possono usare come memoria di lavoro, invece che come cache di codice!
avete presente come nei cartoni animati o altre opere di fantasia i protagonisti attraversino i ponti che crollano dietro di loro? lo stesso col codice.
ad esempio, supponiamo che tutto il codice di inizializzazzione sia contenuto nel settore 0 del arm9, da byte 0 a 511. molto corto, si , ma è un esempio concettuale. il codice del main loop è contenuto nel settore 1, e il codice che controlla il repellente è nel settore 100.
io accendo la console, l'elettronica carica il settore 0 nella memoria 2 e imposta il program counter a tale indirizzo.
alla fine del settore sfocio nel byte 512, che contiene un while(true). quindi non uscir√≤ mai pi√Ļ, e il settore precedente viene liberato e posso usarlo per i miei valori.
poi quando ho il repellente avrò un salto al settore 100, e quando l'elettronica vede che il PC va al di fuori dell'area caricata, me la carica in ram.
certo, ci sono due punti oscuri in questa teoria.
1) ripeto che non ho letto in dettaglio il gbatek, ma mi pare che l'utente abbia il controllo manuale della cache.
2) se mi viene caricato un settore dove prima avevo variabili, che fine fanno? vengono riallocate?

se non risponde completamente si può fondere alle altre teorie!

secondo me dovremmo capire come il gioco carica il ram il resto del codice!
oppure, imparare come funzionano gli homebrew, analizzare un proprio programmino o un emulatore, e capire come funziona l'architettura "di default". quindi dopo possiamo iniziare avvantaggiati sulla rom pokemon? a meno che abbiano usato algoritmi complessi. dopotutto sono uno studio tripla A, con fondi decisamente ingenti, per cui possono permettersi codici complicati.

Guida di cui vado pi√Ļ fiero: Mega-Huge Bonnox' guide (FINALLY expanded)

Se ti sono stato d'aiuto, clicca il tasto "mi piace" qua sotto! Grazie! : )

Il mio nuovo motto: don't ask for ETAs!

[Immagine: yKWdaxi.gif]

(RIP immagine firma cancellata dai server)
"L'uomo √® ancora il pi√Ļ straordinario dei computer"
-
citazione famosa, qualcuno di importante nella storia, forse Churchill boh





*immagine di congiunzione tra mass effect e doctor who della segretaria samantha traynor con in mano lo spazzolino sonico*

Ho un T.A.R.D.I.S. modello 40 ma non so usarlo. Pacman
04-12-2020 08:37 PM
Cerca Cita
 Mi piace ricevuti da: JackHack96 , LD3005
Avon
The down is real
**
Offline

Messaggi: 60
Registrato: 08-09-2014
Reputazione: 2
Mi Piace Ricevuti: 89
Pok√©Money: 160.00‚āĪ
Messaggio: #7
RE: Ricerca routine NDS
(Tutto quello che sto per scrivere riguarda Platino)
Intanto, visto che come avevo detto una teoria promettente mi pareva quella di liberare dei box per avere il loro spazio libero in RAM ho trovato queste cose (sempre con SearchForCheat):
  • Quando si sta andando avanti viene eseguito il codice a 0x25CB52 che legge l'indice del box attuale da un offset volatile (cambia anche in Platino EU!), ci aggiunge 1 e se questo √® >=0x12 viene riportato a 0
  • Quando si sta andando avanti viene eseguito il codice a 0x25CB6C che legge l'indice del box attuale da un offset volatile (cambia anche in Platino EU!), controllaa che questo sia >0, se lo √® ci sottrae 1, altrimenti lo porta a 0x11.
  • Cambiando quindi il valore con il quale viene fatta la compare nel primo punto ed il valore al quale viene settato il box se parte da 0 nel secondo, si possono rendere inaccessibili dei box, in particolare se vogliamo solo avere n box:
    1. All'offset 0x25CB54 mettiamo n (il CMP >= di quando si va in avanti)
    2. All'offset 0x25CB7A mettiamo n-1 (il valore al quale viene portato l'indice se è già a 0 e si prova ad andare all'indietro
  • Incidentalmente, mentre ricercavo queste cose, ho trovato all'offset 0x25CB5C 3 istruzioni curiose:
    Codice:
    ROM:0025CB5C loc_25CB5C                              ; CODE XREF: ROM:0025CB56‚ÜĎj
    ROM:0025CB5C                 LDR             R3, byte_25CB64
    ROM:0025CB5E                 LDR             R0, [R0]
    ROM:0025CB60                 BX              R3
    ROM:0025CB62 ; ---------------------------------------------------------------------------
    ROM:0025CB62                 NOP
    ROM:0025CB62 ; ---------------------------------------------------------------------------
    ROM:0025CB64 byte_25CB64     DCB 0x41                ; DATA XREF: ROM:loc_25CB5C‚ÜĎr
    ROM:0025CB65                 DCB 0x4F ; O
    ROM:0025CB66                 DCB 0x1D
    ROM:0025CB67                 DCB    2
    Notate come venga caricato in r3 un indirizzo di RAM (in questo caso 021D4F41)? ecco, questo vuol dire che il compilatore sa già in fase di compile-time dove verrà ficcato il codice in RAM, che vuol dire che è prevedibile, che vuol dire che segue delle regole, che vuol dire che dobbiamo capire quali siano


  • Seguiranno aggiornamenti (mi auguro) presto riguardo ad almeno uno di questi 3 punti:
    1. Dove sono i box che abbiamo reso inaccessibili in RAM
    2. Disabilitazione di quello che sembra essere un DMA
    3. Algoritmo con il quale si calcola l'indirizzo in RAM a partire da quello nella ROM
    05-12-2020 01:38 PM
    Cerca Cita
     Mi piace ricevuti da: Bonnox , JackHack96 , LD3005
    Bonnox
    Signore del Tempo un po' imbranato
    *****
    Offline

    Messaggi: 2,060
    Registrato: 06-07-2014
    Reputazione: 11
    Mi Piace Ricevuti: 633
    Pok√©Money: 2,329.75‚āĪ
    Messaggio: #8
    RE: Ricerca routine NDS
    ottima notizia!

    ma un dubbio mi attanaglia:

    sei mai riuscito a trovare quelle routine che scopri con nosgba, dentro la ROM?

    se le trovassimo credo che sarebbe molto ma molto pi√Ļ semplice modificare il codice!

    se non le troviamo significa che sono purtroppo compresse o criptate in qualche maniera e caricate al volo


    poi, mi pare strano che il programma che usi ti indichi ROM:0025CB64 come una istruzione. almeno, con IDA, se prima hai un riferimento, dovrebbe trasformarti quel byte in Data o qualcosa di simile. Forse stai usando nosgba che ha una sintassi che richiama IDA ma non mostra bene i riferimenti?
    05-12-2020 04:21 PM
    Cerca Cita
     Mi piace ricevuti da: LD3005
    Avon
    The down is real
    **
    Offline

    Messaggi: 60
    Registrato: 08-09-2014
    Reputazione: 2
    Mi Piace Ricevuti: 89
    Pok√©Money: 160.00‚āĪ
    Messaggio: #9
    RE: Ricerca routine NDS
    Nella frase "viene eseguito il codice a 0x25CB52", 0x25CB52 è un offset della ROM.
    Difatti se ti rechi a quell'offset vedi i byte: 12 78 12 2A 01 D3 che corrispondono a:
    Codice:
    LDRB            R2, [R2]
    CMP             R2, #0x12
    BCC             loc_25CB5C
    Semplicemente ho indicato il punto che ci interessa e non il push (aka l'inizio della routine), se è questo che intendi, perchè non avevo voglia di risalire il codice, ma basta scrollare seguire i vari r14 a ritroso dal no$gba per trovarlo (in questo caso, se proprio ti serve, a 0x258440).

    Ed è la sintassi di IDA
    Perchè il programma che uso è IDA, e lo conta come dato, puoi anche vederlo dal fatto che di fianco ci sia scritto "DATA XREF".
    Se preferisci posso farlo vedere come word ma non so da cosa tu abbia evinto che la conti come un'istruzione
    Codice:
    ROM:0025CB64 dword_25CB64    DCD 0x21D4F41           ; DATA XREF: sub_25CB3C:loc_25CB5C‚ÜĎr

    Nota bene, perchè mi pare che ci fosse confusione anche prima, che non mi sto limitando all'arm9.bin, mi sto riferendo all'intera ROM (cioè al file .nds).
    Se quello che penso √® corretto potremmo fare il 99% del lavoro senza nemmeno doverlo mai toccare, l'arm9.bin, e per quel poco per il quale potrebbe servire ho il dubbio che si possa aggirare, ma vedremo quando avr√≤ fatto pi√Ļ ricerche
    05-12-2020 05:33 PM
    Cerca Cita
     Mi piace ricevuti da: LD3005
    Bonnox
    Signore del Tempo un po' imbranato
    *****
    Offline

    Messaggi: 2,060
    Registrato: 06-07-2014
    Reputazione: 11
    Mi Piace Ricevuti: 633
    Pok√©Money: 2,329.75‚āĪ
    Messaggio: #10
    RE: Ricerca routine NDS
    ahhh, ok, scusa! pensavo che DCD fosse un'istruzione arm, invece sarà un acronimo di IDA per indicare data qualcosa.

    ma perchè tratti la ROM come un pezzo unico quando abbiamo un filesystem? e se modifichiamo i files non cambiano gli indirizzi?
    05-12-2020 05:48 PM
    Cerca Cita
    Avon
    The down is real
    **
    Offline

    Messaggi: 60
    Registrato: 08-09-2014
    Reputazione: 2
    Mi Piace Ricevuti: 89
    Pok√©Money: 160.00‚āĪ
    Messaggio: #11
    RE: Ricerca routine NDS
    Perchè, come ahimè speravo di aver reso chiaro, non sono un hacker NDS né intendo diventarlo: non voglio creare delle modifiche replicabili, sto solo cercando di capire a grandi linee come poter fare lavori simili a quelli che facciamo su GBA per poter facilitare il lavoro a chi verrà e far si che chi sa già come mettere le mai sul GBA possa dare una mano in futuro a chi si approccia al mondo NDS semplicemente applicando un'astrazione o due.

    Ad ogni modo, utilizzare la ROM a scopo di ricerca ha come vantaggio che non mi fa impazzire a fare lo stesso ctrl+F su 100 file, piuttosto posso trovare l'indirizzo che mi serve su una ROM pulita e, se voglio la modifica replicabile, aprire CrystalTile e cercare il file che contiene quell'offset sempre sulla ROM pulita, a quel punto il file da modificare quello è.
    [Immagine: gqAYNXk.png]
    Per esempio posso vedere che per queste due modifiche il file è FSI.CT/overlay9_0019.bin (il codice è a 0x25CB52, il file su una ROM pulita inizia a 0x27C00 ed è lungo ~0xFFFF byte, volendo dire che termina circa a ‭0x37BFF‬: 0x25CB52 è lì in mezzo quindi so che la routine è in questo file.
    Dove?
    Prendo l'offset della routine, ci sottraggo l'offset dell'inizio del file e ho l'offset dentro al file della routine, in questo caso 0x25CB52 - 0x27C00 = 0x4F52
    E guarda guarda, ci sono gli stessi byte (12 78 12 2A 01 D3) che avevo descritto prima:
    Fun fact: nella colonna sub-file ti dice dove verrà caricato in RAM quel file (e ci becca anche, sempre facendo il giochetto di aggiungere 0x4F52 a quell'indirizzo posso trovare esattamente il punto che avevo scoperto su no$gba), ma non capisco da dove prenda l'info.

    VERY fun fact: per controllare che fosse vero ho aperto il desmume e sono andato a quell'indirizzo in ram, aspettandomi di trovare gli ormai famosi 8 byte. Non c'erano.
    MA, ho pensato che queste routine riguardassero il box, quindi ho provato a tenere il memory viewer aperto mentre andavo al box e...
    Yes, il codice viene swappato, uno dei grandi crucci ce li siamo tolti; in teoria mettendo un breakpoint in scrittura sull'indirizzo della routine dovrei anche trovare chi è a fare quello swap (a meno che non sia un processo esterno alla CPU, nel qual caso probabilmente semplicemente crasherà no$).
    Questo mi preoccupa un sacco per√≤, perch√® fare giochetti tipo chiamare la routine che decrypta le info dei pok√©mon potrebbe diventare un sacco pi√Ļ palloso

    I'll keep you posted as soon as ho la voglia di fare le cose
    (Questo messaggio è stato modificato l'ultima volta il: 06-12-2020 01:54 PM da Avon.)
    05-12-2020 06:26 PM
    Cerca Cita
     Mi piace ricevuti da: Bonnox , JackHack96 , Andrea , IvanFGK , LD3005
    Avon
    The down is real
    **
    Offline

    Messaggi: 60
    Registrato: 08-09-2014
    Reputazione: 2
    Mi Piace Ricevuti: 89
    Pok√©Money: 160.00‚āĪ
    Messaggio: #12
    RE: Ricerca routine NDS
    Ok, penso che sarà il penultimo aggiornamento perchè manca veramente solo lo step finale (+ le varie spiegazioni di cosa ho fatto e come, ovviamente) perchè ci siamo quasi.

    Recap di cose che ho capito (sempre tutto per platino, sia EU che US):
    • l'arm9.bin decompresso viene caricato in memoria all'indirizzo 0x2000000 e di l√¨ non si schioda mai, contiene infatti tutte le routine che mi preoccupavo potessero venirmi tolte sotto al culo, tipo quella per (de)criptare i pok√©mon o quella per fai check e setflag.
      • Side note: per decomprimere l'arm9 basta usare la funzione di estrazione di CrystalTile2
      • Side note#2: molto comodo che venga caricato ad un indirizzo 'tondo', perch√® cos√¨ se so che ho una routine a 0x02123456, so che nell'arm9 sar√† a 0x123456; mi basta rimuovere il '0x02' davanti, insomma
    • ci sono vari file 'speciali', chiamati "Overlay" (in maniera abbastanza corretta, direi) che vengono caricati in memoria a seconda di quando serve utilizzarli (tipo uno per la gestione dei box, uno per le battaglie, uno per l'OW, etc...)
    • @LD3005 Mi ha gentilmente fatto notare che esiste gi√† un tutorial di un utente estero in inglese sul come fare a caricare un file in RAM e quindi fondamentlamente sappiamo gi√† come fare ad inserire le routine dove vogliamo
    • Sono abbastanza testardo e ho comunque voluto capire come disabilitare il DMA e dove stia l'indirizzo nel box, quindi:
      1. A 0x021C0794 è contenuto un indirizzo di RAM che fa da "base" per tutti gli altri, e che quindi chiameremo, appunto, base. (per dire, se il Box parte ad un indirizzo A quando [base]=0, allora il box parte all'indirizzo A+0x20 se [base]=0x20; se non è chiaro comunque elaborerò meglio quando scriverò il post con tutti i dettagli).
        Il valore contenuto a questo indirizzo viene cambiato una sola volta, all'avvio del gioco, e viene fatto dalla routine 0x020244c2 (nell'arm9, ovviamente)
      2. Il box parte a 0x228B048 per [base]=0x0227e104 (per rafforzare il concetto sopra, il box parte a 0x228B084 per [base]=0x227e140)
  • Diversamente da i giochi GBA, le entry vuote del box non sono una vasta landa di '0', ma ci sono delle "entry fantasma"
  • Un pok√©mon nel box √® lungo 136 byte, ci sono 6*5=30 pok√®mon in un box, quindi liberando un box risparmiamo 4kb, liberandone 10 risparmiamo 40kb.
    Meno della metà rispetto a quello che offre l'utente estero, ma ho una buona idea di come questo potrebbe venir sfruttato.
  • Va ancora trovato la rotuine che fa i check sui box (che sono praticamente certo esista) e capire come cambiarla per non effettuare pi√Ļ check sui box che disabilitiamo
  • Cercando su internet ho trovato un disassembly di platino vecchio di 4 anni ma davvero molto completo (soprattutto per quanto riguarda l'arm9) che mi ha aiutato abbastanza, quindi crediti anche ai due contributor di quel progetto: JimB16 e huderlem


  • I'll be back soon
    (Questo messaggio è stato modificato l'ultima volta il: 12-12-2020 05:36 PM da Avon.)
    07-12-2020 07:56 PM
    Cerca Cita
     Mi piace ricevuti da: Bonnox , JackHack96 , LD3005
    Avon
    The down is real
    **
    Offline

    Messaggi: 60
    Registrato: 08-09-2014
    Reputazione: 2
    Mi Piace Ricevuti: 89
    Pok√©Money: 160.00‚āĪ
    Messaggio: #13
    RE: Ricerca routine NDS
    Come promesso, questo sarà l'ultimo post mio su questo topic (a meno di eventuali chiarimenti, ma fondamentalmente non proseguirò la ricerca perchè mi pare completa): ne aprirò al massimo uno in "Guide" con tutte le informazioni ordinate e dettagliate.
    Ora, premessa (e rant) obbligatoria da scrivere quanto facoltativa da leggere:
    ll nome che affibierò alle routine e i loro indirizzi derivano tutti da PokePlat, un progetto di decomp di Platino di JimB16 e huderlem, quindi veramente un sacco di crediti a loro.
    (Non tutte tutte le routine in realtà, ma anche quelle che ho trovato io derivano dall'incredibile lavoro di questi due ragazzi, quindi comunque crediti a loro)

    Premessa: per semplicità durante la stesura, vorrei definire un paio di shortcut che mi aiuteranno ad esprimermi al meglio:
    • Come di consueto, 0x? indica un numero esadecimale e ? (senza il prefisso 0x) indica un numero decimale
    • 9xAAAAAAAA: Se vedete un indirizzo iniziare per 9x (tipo 9x252E8) vuol dire che la routine si trova all'indirizzo 0xAAAAAAAA nel file arm9.bin (quindi nell'esempio, all'indiirzzo 0x252E8) e che vuole di conseguenza dire che si trova all'indirizzo 0x400 + 0xAAAAAAAA nella ROM e viene caricata all'indirizzo 0x02000000 + 0xAAAAAAAA nella RAM
    • RxAAAAAAAA: Se vedete un indirizzo iniziare per Rx, indica che l'indirizzo riguarda la ROM (si pu√≤ facilmente ricavare il file al quale appartiene con un metodo spiegato precedentemente)
    • oAAxBBBBBBBB: Se vedete un indirizzo iniziare per oAAx, vuol dire che sto parlando dell'indirizzo 0xBBBBBBBB nel file Overlay AA (Quindi, per dire o75x1C4 vuol dire 0x1C4 posizioni dopo l'indirizzo dell'overlay 75 (75 decimale).
    • bxAAAAAAAA: Se vedete un indirizzo iniziare per bx, vuol dire che sto parlando dell'indirizzo 0xAAAAAAAA posizioni dopo rispetto all'indirizzo base (l'indirizzo di base √® contenuto all'offset 0x021C0794 nelle ROM Platino)
    • malloc e free sono i nomi di due subroutine, arrivano dritti dal C e fanno esattamente quello che fanno in quell'ambito: malloc alloca della memoria per utilizzi futuri (assicurandosi non ci siano sovvrapposizioni) e free libera queste aree di memoria per eventualmente essere riallocate.
      Malloc si trova a 9x18140 e free a 9x181C0.


    Allora, in parole povere il risultato finale consiste nell'avere uno spazio in RAM che non cambi (dei box che rimuoviamo dal gioco) e che si possa manipolare a nostro piacimento, verosimilmente per inserire routine e poi richiamarle (le stesse routine che nel GBA vengono inserite nello "spazio libero della ROM"); la maniera pi√Ļ veloce √® stata quella di caricare un file nella RAM e scegliere un file da "sacrificare" a questo scopo.
    Per ottenre questo ho dovuto:
    1. Capire dove e come vengano modificati i box:
      • La prima locazione ovvia √® quando si accede ad un determinato box, GF decripta i dati per capire di che specie si tratti in maniera da mostrare il minisprite corretto.
        Disabilitazione: spiegata in un post precedente, si tratta di hex editing agli indirizzi 0x25CB54 e 0x25CB7A
      • La seconda √® il meccanismo di caricamento del salvataggio che avviene non so bene dove, perch√® non ci interessa disabilitarlo: se carichiamo l√¨ il file la prima volta, quando verr√† caricato il salvataggio non ci sar√† nessun check e verr√† semplicemente caricato il file
      • Al primo avvio, quando si preme "start" sullo schermo con Giratina, viene chiamata la routine "ClearAllVariableAreas", all'indirizzo 9x252E8 che "pialla" tutte le aree coperte da DMA (vedremo dopo che i box sono coperti da DMA) e poi le inizializza in base alla loro funzione.
        Questa funzione viene chiamata a sua volta da da "InitVariablesForNewGame": o57x1C4, o, quando è caricata in memoria, 0x21d0f44.
    2. Capire dove siano i box: I box sono protetti da DMA; sfortunamente questo non è un semplice meccanismo di sicurezza come nei giochi di terza gen, ma è una vera e propria necessità: i box fanno infatti parte di quella che PokePlat chiama "VariableAreas", delle aree di memoria che sono dinamicamente allocate tramite malloc.
      Questo vuol dire che non possiamo (o meglio, è talmente complicato che non ho assolutamente voglia nemmeno di iniziare a pensare come si potrebbe) disabilitare la DMA, e dovremo semplicemente gestire l'inconvenienza.
      Fortunatamente l'indirizzo di inizio di queste VariableAreas viene salvato all'indirizzo 0x021C0794 (questo indirizzo è ciò al quale ci riferremo come base , quindi base=[0x021C0794 ]; la base spesso inizia con 0x0227E??? e variano solo le ultime 3 cifre), e con un po' di test ho scoperto che i box partono esattamente 0xCF44 byte dopo la base (in altre parole, i box partono a bxCF44).
      Visto che comunque la posizione dei box non cambia mai rispetto alla base e questa è sempre accessibile, possiamo considerarla una semi-vittoria.
    3. capire come vengano caricati i NARC in memoria e come funzionino: qui è bastato semplciemente leggersi un po' il fantastico GBATek e il codice in PokePlat per capire come vengano gestiti i NARC.
      Per farla veramente breve, esiste una struttura in memoria (assegnata tramite malloc) per ogni Narc caricato in file che PokePlat chiama "NarcFileHandler", ed è fatta un po' così (dove mancano gli offset sono tutti unknown, ma non dovrebbero importare troppissimo):
      Codice:
      //sizeof: 0x54
      struct NarcFileHandler{
          /*OFF: 0x24  */u32 fileStartInROM;
          /*OFF: 0x28  */u32 fileEndInROM;
          /*OFF: 0x2C  */u32 SeekPtr;
          
          /*OFF: 0x38  */u8  readSize_maybe;
          
          /*OFF: 0x48  */u32 startOfBTAF;
          /*OFF: 0x4C  */u32 IMGStartPtr;
          /*OFF: 0x50  */u8  numberOfFiles;
          
      };
      Questa struttura viene utilizzata per effettuare le letture da ROM in RAM, in particolare dalle routine "LoadFromNarc_8" (9x6C20) e "LoadFromNarc" (9x6AA0), dove la prima effettua un malloc per il NarcFileHandler e ti ritorna l'indirizzo dello stesso inizializzato e la seconda invece carica direttamente in un indirizzo di memoria specificato da noi un file X contenuto in un narc Y.


    Una volta smarcati tutti questi punti, la strategia utilizzata è la seguente: dovremmo scegliere un file da "sacrificare" e caricarlo all'indirizzo dell' n-esimo box (dopo aver disabilitato tutti i box >=n). Visto che però i box vengono piallati da "InitVariablesForNewGame", dovremmo caricare questo file DOPO questa routine.
    Guardando la gerarchia di chiamate, è possibile notare che il posto milgiore per modificare questo comportamento sia proprio la routine "InitVariablesForNewGame", perchè risalendo troppo si va a toccare funzionamenti generici, e risalendo troppo poco le routine presenti nell'o57 sono troppo corte e ci danno poco spazio di manovra per fare quello che vogliamo.
    Notiamo che la routine "InitVariablesForNewGame" ci da comunque pochissimo spazio di manovra, perchè spanna appena da o57x1C4 a o57x1F0, dandoci solo 0x2C byte di spazio con i quali lavorare; let's get creative!
    L'idea è questa:
    1. Utilizzare l'allocazione dinamica del NDS a nostro favore e utilizzare il malloc per predispore un'are di "staging" della grandezza che ci pare
    2. Modificare il file che abbiamo deciso di sacrificare per contenere la routine "InitVariablesForNewGame" e, successivamente al piallamento dei dati, il caricamento di questo stesso file nell'indirizzo dei box (si, stiamo sprecando una parte del nostro file per replicare "InitVariablesForNewGame", ma si parla di una 30ina di byte)
    3. Caricare il file nell'area di staging, fare un BX all'inizio di quel file per eseguire "InitVariablesForNewGame" e allocare il nostro file nel box
    4. Tornare all'esecuzione normale, ma con il nostro file caricato nei box.


    Dopo un po' di tentativi, ecco le due routine (la prima da sosituire a "InitVariablesForNewGame", la seconda da inserire nel file sacrificale):

    PS: Ho utilizzato il file data/underg_radar.narc/underg_radar-0.NCLR perchè credo, giudicando dal nome, che abbia a che fare con il sottosuolo e, diciamocelo, che ce ne frega?
    Il NARC ha id 0x1c, l'ho preso dritto dal fle constants/narc_constants.s di PokePlat).
    Routine 1 (Con spiegazione):

    Routine 2 (Senza spiegazione perchè non credo ci sia molto da spiegare):

    Inserendo la prima rotuine compilata a o57x1C4 (o, su una rom pulita, Rx3501C4) e la seconda all'inizio del file designato (nel mio caso, in una rom vergine, a Rx65026C), potremo notare che dopo aver premuto start avremo il nostro file caricato all'indirizzo del box libero!

    Da qui in poi possiamo iniziare a trattare il nostro file 'sacrificale' alla stregua dello spazio libero di una ROM GBA, e comportarci esattamente alla stessa maniera.

    Nota: potrebbe essere una buona idea chiamare free sullo spazio allocato dalla prima routine, perchè credo ci sia un limite al numero di aree allocate da malloc che si possono avere in contemporanea.


    Avon out, per ora.
    Prima o poi giuro che scriverò un post dove condividerò tutto quello che ho imparato nei 5425413655887 tentativi andati male di implementare questa cosa.
    Prima o poi.
    Ora non voglio pi√Ļ vedere un gioco NDS per almeno 10 anni.
    13-12-2020 07:58 PM
    Cerca Cita
     Mi piace ricevuti da: PacoScarso , Boh , Flygon , Bonnox , Andrea , JackHack96 , LD3005
    Rispondi 




    Utente(i) che stanno guardando questa discussione: 1 Ospite(i)
    Powered by MyBB, © 2002-2021 MyBB Group. Copyright ¬© | 2021 Pok√©mon Hacking