Registrarse

[RUBY] IV/EV Viewer

Andrea

Leyenda de WaH
Goodmorning everyone.
Today i would like to release the code of a feature that i developed a few days ago, that is a "visualizer" of IV/EV of the pokémon of our team.
Since this implementation is quite complex, i decided to abandon the ASM and develop it entirely in C.
Here is the code:
Código:
#include "include/gba_types.h"
#include "include/gba_keys.h"

//definizione di alcuni offset utili
#define StartROM	0x08700000
#define TIMER		((u8*)0x02024F50)
#define PALRAM		((u16*)0x0202EEC8)
#define PALFADE		((u16*)0x0202EAC8)
#define CANVAS		((u32*)0x0600CC80)
#define RAW		((u16*)0x0600F9A0)
#define BLANK		0xEEEEEEEE
#define STATS		((u32*)0x08710000)
#define AMOUNT		(*(u8*)0x03004350)
#define FIRST_SLOT	0x03004360
#define SIZE_OF_SLOT	100
#define HP		0x27		//IV
#define HP_2		0x1A		//EV
#define BACKGROUND_PAL 	0x77BD
#define BUFFER		0x02024F80

#define DEFAULT 0	//1 se si vuole mantenere la grafica originale di pokémon rubino
#define ISTO	1	//1 se si vuole l'istogramma come metodo di visualizzazione delle IV/EV

#if ISTO		//se voglio l'istogramma
	#define SX	0x58
	#define SY	0x28
	#define SHIFT	0x88
#endif

/*
table STATS
EB F7 40 08 10 00 98 00 D9 F7 40 08 38 00 C8 00
E1 F7 40 08 58 00 C8 00 D5 F7 40 08 70 00 90 00
DD F7 40 08 58 00 58 00 E5 F7 40 08 38 00 58 00
*/

//dichiarazione dei prototipi delle funzioni

//protitipi per grafico con espansione
void initCanvas(u8 page);
void drawCanvas();
void setPixel(u8 x, u8 y, u8 color);
int getPixel(u8 x, u8 y);
void drawLine(u8 x1, u8 y1, u8 x2, u8 y2, u8 color);
void paintArea(u8 x1, u8 y1, u8 x2, u8 y2, u8 color);
void swap(u8* a, u8* b);

//protitipi per grafico con espansione
void drawBar(u8 x, u8 y, u8 value);
void vid_vsync();

//protitipi generali
int div(u32 a, u32 b);
int mod(u32 a, u32 b);
int module(int a);
void printString(u32 offset, u8 x, u8 y);
int getPokemonData(u32 slot, u8 field, u8 zero);
int getMonGender(u32 slot);
void intToString(u32 buffer, u32 number);
void printString2(u32 buffer, u8 x, u8 y);
void printString3(u32 string, u8 x, u8 y, u8 color);
void printString4(u32 string, u8 x, u8 y, u8 color);
void refreshInfo(u8 current, u8 page);
void showbox(u8 x, u8 y, u8 L, u8 H);
void hidebox(u8 x, u8 y, u8 L, u8 H);
int maxValue(int arr[]);
int minValue(int arr[]);
void pic(u8 zero, u8 x, u8 y);
void closePic();
u32 oam(u32 value);
void sound(u16 s);
void cry(u16 cr);
void callback(void* addr);
void LZ77UnCompVram(u32 source, u32 dest);
void CpuFastSet(u32 source, u32 dest, u32 size);

void main();
void main2();

/*
Primo main, utilizzato per:
- visualizzare l'immagine del primo pokémon in squadra mediante la funzione pic().
- setto il TIMER a 0
- richiamo il secondo main
*/
void main()
{
    pic(getPokemonData(FIRST_SLOT, 0xB, 0), 0, 6);
    TIMER[0] = 0;
    callback((void*)(StartROM + main2 + 1));
}

/*
Secondo main, Il TIMER menzionato in precedenza serve per scandire due fasi:
- la prima, in cui verrà corretta la paletta dello sprite visualizzato attraverso pic()
- la seconda, il cui compito principale è quello di ricevere gli input da parte dell'utente ed elaborarli
*/
void main2()
{
    //seconda fase
    if (TIMER[0] == 0x2) {
        //current indica lo slot pokémon in cui ci si trova attualmente
        //pageIV, 0 visualizzazione IV, 1 visualizzazione EV
        int current = 0, pageIV = 0;

        refreshInfo(current, pageIV);

        //ciclo while per ricevere gli input dei tasti
        while (keyDown(KEY_B))
            ;
        while (!keyDown(KEY_B)) {
            if (keyDown(KEY_DOWN)) {
                while (keyDown(KEY_DOWN))
                    ;
                if (current < (AMOUNT - 1)) {
                    sound(5);
                    current++;
                    refreshInfo(current, pageIV);
                }
            }
            else if (keyDown(KEY_UP)) {
                while (keyDown(KEY_UP))
                    ;
                if (current > 0) {
                    sound(5);
                    current--;
                    refreshInfo(current, pageIV);
                }
            }
            else if (keyDown(KEY_RIGHT)) {
                while (keyDown(KEY_RIGHT))
                    ;
                if (!pageIV) {
                    sound(5);
                    pageIV = 1;
                    refreshInfo(current, pageIV);
                }
            }
            else if (keyDown(KEY_LEFT)) {
                while (keyDown(KEY_LEFT))
                    ;
                if (pageIV) {
                    sound(5);
                    pageIV = 0;
                    refreshInfo(current, pageIV);
                }
            }
        }
        //una volta chiuso il menu con il tasto B, elimino lo sprite e pulisco il BG
        closePic();
        hidebox(0, 0, 0x1E, 0x13);
        //ripristino il callback con il valore originale
        callback((void*)0x0807CA35);
    }
    else if (TIMER[0] == 1) //prima fase
    {
        //ricerco lo sprite all'interno della ram
        u32 sprite = oam((*(u32*)0x07000000));
        //correggo la paletta dell'oam
        (*(u16*)(sprite + 4)) = ((*(u16*)(sprite + 4)) & 0xFFF) | 0xE000;

#if !DEFAULT
        CpuFastSet(0x08XXXXXX, 0x0202F068, 8); //palette
        CpuFastSet(0x08XXXXXX, 0x0202EE68, 8); //palette
        LZ77UnCompVram(0x08YYYYYY, 0x0600D480); //tile
        (*(u16*)0x0202F0A4) = BACKGROUND_PAL;
        (*(u16*)0x0202EEA4) = BACKGROUND_PAL;
        (*(u16*)0x0202F0C6) = BACKGROUND_PAL;
        (*(u16*)0x0202EEC6) = BACKGROUND_PAL;
#endif

#if ISTO
        CpuFastSet(0x08ZZZZZZ, 0x0600CC80, 0x30);
#endif
        TIMER[0]++;
    }
    else
        TIMER[0]++;
}

//Funzione che si occupa dell'aggiornamento delle informazioni
void refreshInfo(u8 current, u8 page)
{
    int x, y, statValues[6], points[2][7], progressPoints[2][7];

#if DEFAULT
    showbox(0, 0, 0x1D, 0x13);
#else
    CpuFastSet(0x08KKKKKK, 0x0600F800, 0x140); //raw totale
    if (page)
        CpuFastSet(0x08JJJJJJ, 0x0600F800, 0x20); //raw barra page EV
#endif

#if !ISTO
    //funzione utilizzata per inizializzare il riquadro su cui verrà disegnato il grafico
    initCanvas(page);
    //visualizzo il riquadro
    drawCanvas();
#endif

    //riempio array con le IV o EV del pokemon
    for (x = 0; x < 6; x++)
        statValues[x] = getPokemonData(FIRST_SLOT + (current * SIZE_OF_SLOT), (!page ? HP + x : HP_2 + x), 0);

    //flag utilizzate per colorare solamente una singola statistica, nel caso un cui ci fossero più statistiche con lo stesso valore minimo/massimo
    int maxSet = 0, minSet = 0;
    //ottengo il valore massimo e minimo delle IV/EV
    //questo mi servirà per andare a colorare di rosso/blu la statistica minima/massima
    int max = maxValue(statValues);
    int min = minValue(statValues);

//print stringhe e valori IV/EV
#if !ISTO
    for (x = 0; x < 6; x++) {
        if (((statValues[x] != max) || maxSet) && ((statValues[x] != min) || minSet))
            printString(STATS[(x << 1)], (STATS[(x << 1) + 1] >> 16), STATS[(x << 1) + 1] & 0xFFFF);
        else {
            if ((statValues[x] == max) && !maxSet) {
                printString4(STATS[(x << 1)], (STATS[(x << 1) + 1] >> 16), STATS[(x << 1) + 1] & 0xFFFF, 0x4);
                maxSet = 1;
            }
            else if (!minSet) {
                printString4(STATS[(x << 1)], (STATS[(x << 1) + 1] >> 16), STATS[(x << 1) + 1] & 0xFFFF, 0x2);
                minSet = 1;
            }
        }

        intToString(BUFFER, statValues[x]);
        printString2(BUFFER, ((STATS[(x << 1) + 1] >> 16) >> 3) + 3 - (!x ? 2 : 0) - ((x) && (x < 3) ? 1 : 0), ((STATS[(x << 1) + 1] & 0xFFFF) >> 3) + 2);
    }
#else //ISTOGRAMMA
    for (x = 0; x < 6; x++) {
        if (((statValues[x] != max) || maxSet) && ((statValues[x] != min) || minSet))
            printString(STATS[(x << 1)], SX, SY + (x << 4));
        else {
            if ((statValues[x] == max) && !maxSet) {
                printString4(STATS[(x << 1)], SX, SY + (x << 4), 0x4);
                maxSet = 1;
            }
            else if (!minSet) {
                printString4(STATS[(x << 1)], SX, SY + (x << 4), 0x2);
                minSet = 1;
            }
        }

        intToString(BUFFER, statValues[x]);
        printString2(BUFFER, (SX >> 3) + (SHIFT >> 3), (SY >> 3) + (x << 1));
    }
#endif

    //print numero pokémon attuale
    intToString(BUFFER, (current + 1));
    intToString(BUFFER + 2, AMOUNT);
    (*(u8*)(BUFFER + 1)) = 0xBA; // char '/'
    printString2(BUFFER, 4, 0x10);

#if DEFAULT
    if (!page)
        printString2(0x081F502F, 28, 2);
    else
        printString2(0x0840303A, 28, 2);
#endif

    //ottengo tutti i dati del pokemon che sto tentando di visualizzare e i dati relativi all'oam
    u32 sprite = oam((*(u32*)0x07000000));
    u16 specie = getPokemonData(FIRST_SLOT + (current * SIZE_OF_SLOT), 0xB, 0);
    u8 gender = getMonGender(FIRST_SLOT + (current * SIZE_OF_SLOT));
    u32 tile = ((*(u16*)(sprite + 4)) & 0xFFF);
    u32 pal = ((((*(u16*)(sprite + 4)) & 0xF000) >> 12) << 5) + 0x0202F0C8;

    //update dello sprite del pokemon e print del nome, sesso e livello del pokemon
    if (!getPokemonData(FIRST_SLOT + (current * SIZE_OF_SLOT), 0x2D, 0)) {
        LZ77UnCompVram((*(u32*)(0x081E8354 + (specie << 3))), ((tile << 5) + 0x06010000));
        LZ77UnCompVram((*(u32*)(0x081EA5B4 + (specie << 3))), pal);
        printString2((FIRST_SLOT + 8) + (current * SIZE_OF_SLOT), 8, 2);
        if (!gender) {
            printString3(0x0820C33D, 3, 4, 0xE);
        }
        else if (gender == 0xFE) {
            printString3(0x0820C33F, 3, 4, 2);
        }

        printString2(0x081005C6, 5, 4); //LV
        intToString(BUFFER, (*(u8*)(FIRST_SLOT + (current * SIZE_OF_SLOT) + 0x54)));
        printString2(BUFFER, 8, 4);
        cry(specie);
    }
    else //nel caso in cui fosse un uovo
    {
        for (x = 0; x < 0x200; x++)
            (*(u32*)((tile << 5) + 0x06010000 + (x << 2))) = 0;
        for (x = 0; x < 4; x++)
            CpuFastSet(0x08209AF8 + (x << 7), ((tile << 5) + 0x06010240) + (x << 8), 0x20);
        CpuFastSet(0x08209AD8, pal, 8);
        printString2(0x083B26B0, 8, 2);
    }

#if !ISTO //GRAFICO CON ESPANSIONE
    //calcolo punti grafo
    for (x = 0; x < 6; x++) {
        if (!x) {
            //sopra
            points[0][x] = 31;
            if (!page)
                points[1][x] = 21 - div((21 * statValues[x]), 31);
            else
                points[1][x] = 21 - (div((21 * statValues[x]), 31) >> 3);

            progressPoints[0][x] = 31;
            progressPoints[1][x] = 21;
        }
        else if (x == 3) {
            //sotto
            points[0][x] = 31;
            if (!page)
                points[1][x] = 41 + div((21 * statValues[x]), 31);
            else
                points[1][x] = 41 + (div((21 * statValues[x]), 31) >> 3);
            progressPoints[0][x] = 31;
            progressPoints[1][x] = 41;
        }
        else if (x < 3) {
            //destra
            if (!page)
                points[0][x] = 32 + statValues[x];
            else
                points[0][x] = 32 + (statValues[x] >> 3);
            points[1][x] = 21 + ((1 - mod(x, 2)) * 21);

            progressPoints[0][x] = 32;
            progressPoints[1][x] = points[1][x];
        }
        else {
            //sinistra
            if (!page)
                points[0][x] = 31 - statValues[x];
            else
                points[0][x] = 31 - (statValues[x] >> 3);
            points[1][x] = 21 + ((1 - mod(x, 2)) * 21);

            progressPoints[0][x] = 31;
            progressPoints[1][x] = points[1][x];
        }
    }

    //ricopio il primo punto del grafo
    //questo mi permette di avere un riferimento circolare tra i punti, utile per alleggerire il ciclo for
    points[0][6] = points[0][0];
    points[1][6] = points[1][0];
    progressPoints[0][6] = points[0][0];
    progressPoints[1][6] = points[1][0];

    //nel caso stia visualizzando le EV, riduco la scala di visualizzazione di 8 (shift >> 3).
    if (page)
        max = max >> 3;

    //animazione grafo
    for (y = 0; y < max; y++) {
        for (x = 0; x < 6; x++) {
            drawLine(progressPoints[0][x], progressPoints[1][x], progressPoints[0][x + 1], progressPoints[1][x + 1], 1);
            if (!x) {
                if (progressPoints[1][x] > points[1][x])
                    progressPoints[1][x]--;
                progressPoints[0][6] = progressPoints[0][0];
                progressPoints[1][6] = progressPoints[1][0];
            }
            else if (x == 3) {
                if (progressPoints[1][x] < points[1][x])
                    progressPoints[1][x]++;
            }
            else if (x < 3) {
                if (progressPoints[0][x] < points[0][x])
                    progressPoints[0][x]++;
            }
            else {
                if (progressPoints[0][x] > points[0][x])
                    progressPoints[0][x]--;
            }
            paintArea(progressPoints[0][x], progressPoints[1][x], progressPoints[0][x + 1], progressPoints[1][x + 1], 1);
        }
    }
#else //ISTOGRAMMA
    int progressStat[6] = { 0, 0, 0, 0, 0, 0 };
    if (page)
        max = max >> 3;

    for (y = 0; y <= max; y++) {
        vid_vsync();
        for (x = 0; x < 6; x++) {
            drawBar((SX >> 3) + 5, (SY >> 3) + (x << 1), progressStat[x]);
            if (progressStat[x] < statValues[x])
                progressStat[x]++;
        }
    }
#endif
}

#if !ISTO
/*
Funzione utilizzata per disegnare una retta all'interno del riquadro
Suddivisione in 3 casi:
- rette orizzontali
- rette verticali
- rette completamente verticali
*/
void drawLine(u8 x1, u8 y1, u8 x2, u8 y2, u8 color)
{
    u8 add = 0;
    u8 lastMod = 0;
    int x, y;
    if (x2 < x1) {
        swap(&x1, &x2);
        swap(&y1, &y2);
    }
    int coeff = div(((y2 - y1)), ((x2 - x1)));
    int coeff_2 = mod(((y2 - y1)), ((x2 - x1)));

    if (x1 == x2) {
        //caso speciale, retta completamente verticale
        if (y2 < y1)
            swap(&y1, &y2);
        for (y = y1; y <= y2; y++)
            setPixel(x1, y, color);
    }
    else if (coeff == 0) {
        //rette orizzontali
        u8 segment = module(y2 - y1) + 1;
        u8 len = div((x2 - x1), segment);
        u8 modd = mod((x2 - x1), segment);
        u8 adding = div(segment, modd);
        int yDec = (y1 <= y2) ? 1 : -1;
        for (y = y1; (((yDec == 1) && (y <= y2)) ? 1 : 0) || (((yDec != 1) && (y >= y2)) ? 1 : 0); y += yDec) {
            for (x = (x1 + (module(y - y1) * len) + lastMod); x < ((x1 + len) + (module(y - y1) * len) + ((modd && (!mod(add, adding))) ? 1 : 0) + lastMod); x++) {
                setPixel(x, y, color);
            }
            if (!mod(add, adding)) {
                if (modd > 0) {
                    lastMod++;
                    modd--;
                }
            }

            add++;
        }
        setPixel(x2, y2, color);
    }
    else {
        //rette verticali
        if (coeff <= 0) {
            swap(&x1, &x2);
            swap(&y1, &y2);
        }
        u8 segment = module(x2 - x1) + 1;
        u8 len = div((y2 - y1), segment);
        u8 modd = mod((y2 - y1), segment);
        u8 adding = div(segment, modd);
        int xDec = (coeff > 0) ? 1 : -1;
        for (x = x1; (((xDec == 1) && (x <= x2)) ? 1 : 0) || (((xDec != 1) && (x >= x2)) ? 1 : 0); x += xDec) {
            for (y = (y1 + (module(x - x1) * len) + lastMod); y < ((y1 + len) + (module(x - x1) * len) + ((modd && (!mod(add, adding))) ? 1 : 0) + lastMod); y++) {
                setPixel(x, y, color);
            }

            if (!mod(add, adding)) {
                if (modd > 0) {
                    lastMod++;
                    modd--;
                }
            }

            add++;
        }
        setPixel(x2, y2, color);
    }
}

void initCanvas(u8 page)
{
    int x;
    for (x = 0; x < 0x200; x++)
        CANVAS[x] = BLANK;
    drawLine(31, 0, 31, 63, 8);
    drawLine(32, 0, 32, 63, 8);
    drawLine(0, 21, 63, 21, 8);
    drawLine(0, 42, 63, 42, 8);
}

void drawCanvas()
{
    int x, y;
    for (y = 0; y < 8; y++) {
        for (x = 0; x < 8; x++) {
            RAW[x + ((y << 6) >> 1)] = (0xE264 + (x + (y << 3)));
        }
    }
}

int getPixel(u8 x, u8 y)
{
    u8 shift = ((x - ((x >> 3) << 3)) << 2);
    u32 offset = (((x >> 3) << 5) >> 2) + (((y >> 3) << 8) >> 2) + (y - ((y >> 3) << 3));
    u32 lastValue = CANVAS[offset];
    return ((lastValue & (0xF << shift)) >> shift);
}

void setPixel(u8 x, u8 y, u8 color)
{
    u8 shift = ((x - ((x >> 3) << 3)) << 2);
    u32 offset = (((x >> 3) << 5) >> 2) + (((y >> 3) << 8) >> 2) + (y - ((y >> 3) << 3));
    u32 lastValue = CANVAS[offset];
    u32 mask = (lastValue & (~(0xF << shift)));
    CANVAS[offset] = mask | ((color & 0xF) << shift);
}

void paintArea(u8 x1, u8 y1, u8 x2, u8 y2, u8 color)
{
    int x, y, first[2], last[2], boolFirst = 0;
    //apparentemente la funzine swap è parecchio onerosa
    //if (y2 < y1) swap(&y1, &y2);
    //if (x2 < x1) swap(&x1, &x2);

    int xDec = (x1 <= x2) ? 1 : -1;
    int yDec = (y1 <= y2) ? 1 : -1;
    for (y = y1; y != y2; y += yDec) {
        for (x = x1;
             (x != x2); x += xDec) {
            if (getPixel(x, y) == color) {
                if (!boolFirst) {
                    first[0] = x;
                    first[1] = y;
                }
                else {
                    last[0] = x;
                    last[1] = y;
                }
                boolFirst++;
            }
        }
        if (boolFirst > 1)
            drawLine(first[0], first[1], last[0], last[1], 1);
        boolFirst = 0;
    }
}

void swap(u8* a, u8* b)
{
    u8 temp = *a;
    *a = *b;
    *b = temp;
}

#else //ISTOGRAMMA, funzioni di visualizzazione
void drawBar(u8 x, u8 y, u8 value)
{
    if (value >= 31)
        value = 32;
    u8 full = (value >> 2);
    u8 rest = mod(value, 4);
    int i, k;
    for (k = 0; k < 2; k++) {
        u32 SRAW = 0x0600F800 + (x << 1) + (y << 6);
        (*(u16*)(SRAW + (k << 6))) = 0xE269 | (k << 11); 				//bordo sinistra
        SRAW += 2;
        for (i = 0; i < full; i++)
            (*(u16*)(SRAW + (i << 1) + (k << 6))) = 0xE268 | (k << 11); 		//tile pieno
        if (full < 8)
            (*(u16*)(SRAW + (full << 1) + (k << 6))) = (0xE264 + rest) | (k << 11); 	//tile resto
        for (i = (full + 1); i < 8; i++)
            (*(u16*)(SRAW + (i << 1) + (k << 6))) = 0xE264 | (k << 11); //tile vuoto
        //(*(u16*)(SRAW+(8<<1)+(k<<6))) = 0xE269 | (k<<11) | 0x400;			//bordo destra
    }
}

void vid_vsync()
{
    __asm("SWI 0x5");
}
#endif

int div(u32 a, u32 b)
{
    int (*func)(void) = (int (*)(void))0x081E0868 + 1;
    return func();
}

int mod(u32 a, u32 b)
{
    int (*func)(void) = (int (*)(void))0x081E0F08 + 1;
    return func();
}

int module(int a)
{
    if (a >= 0)
        return a;
    return (-a);
}

void printString(u32 offset, u8 x, u8 y)
{
    int (*func)(void) = (int (*)(void))0x08072A18 + 1;
    func();
}

int getPokemonData(u32 slot, u8 field, u8 zero)
{
    int (*func)(void) = (int (*)(void))0x0803CB60 + 1;
    return func();
}

int getMonGender(u32 slot)
{
    int (*func)(void) = (int (*)(void))0x0803C4B8 + 1;
    return func();
}

void intToString(u32 buffer, u32 number)
{
    int (*func)(void) = (int (*)(void))0x08006DDC + 1;
    func();
}

void printString2(u32 buffer, u8 x, u8 y)
{
    int (*func)(void) = (int (*)(void))0x08072B4C + 1;
    func();
}

void copyString(u32 offset, u32 offset_2)
{
    int (*func)(void) = (int (*)(void))0x08006AB0 + 1;
    func();
}

void printString3(u32 string, u8 x, u8 y, u8 color)
{
    (*(u16*)BUFFER) = 0x01FC;
    (*(u16*)(BUFFER + 2)) = color;
    copyString(BUFFER + 3, string);
    printString2(BUFFER, x, y);
}

void printString4(u32 string, u8 x, u8 y, u8 color)
{
    (*(u16*)BUFFER) = 0x01FC;
    (*(u16*)(BUFFER + 2)) = color;
    copyString(BUFFER + 3, string);
    printString(BUFFER, x, y);
}

void showbox(u8 x, u8 y, u8 L, u8 H)
{
    int (*func)(u8, u8, u8, u8) = (int (*)(void))0x08071F08 + 1;
    func(x, y, L, H);
}

void hidebox(u8 x, u8 y, u8 L, u8 H)
{
    int (*func)(u8, u8, u8, u8) = (int (*)(void))0x08071E84 + 1;
    func(x, y, L, H);
}

void cry(u16 cr)
{
    int (*func)(u16) = (int (*)(void))0x08075178 + 1;
    func(cr);
}

void sound(u16 s)
{
    int (*func)(u16) = (int (*)(void))0x08075494 + 1;
    func(s);
}

void pic(u8 zero, u8 x, u8 y)
{
    int (*func)(u8, u8, u8) = (int (*)(void))0x080B58C4 + 1;
    func(zero, x, y);
}

void closePic()
{
    int (*func)(void) = (int (*)(void))0x080B5974 + 1;
    func();
}

u32 oam(u32 value)
{
    int i = 0;
    while ((*(u32*)(0x02020004 + (i * 0x44))) != value)
        i++;
    return (0x02020004 + (i * 0x44));
}

void callback(void* addr)
{
    (*(u32*)0x03004B20) = (u32)addr;
}

void LZ77UnCompVram(u32 source, u32 dest)
{
    __asm("SWI 0x12");
}

void CpuFastSet(u32 source, u32 dest, u32 size)
{
    __asm("SWI 0xC");
}

int maxValue(int arr[])
{
    int i, max = 0;
    for (i = 0; i < 6; i++)
        if (arr[i] > max)
            max = arr[i];
    return max;
}

int minValue(int arr[])
{
    int i, min = arr[0];
    for (i = 1; i < 6; i++)
        if (arr[i] < min)
            min = arr[i];
    return min;
}
The gba_types.h and gba_keys.h libraries can be found at the following link: : https://github.com/shinyquagsire23/FR-CrystalIntro/tree/master/include

Before showing you the implementation in operation, i would like to explain to you broadly how the code works:
At the beginning of the code there are the declarations of some useful offsets that we will use later.
The most important are StartROM, which will contain the offset in which you will insert the compiled code and STATS, a table that will contain the pointers and the coordinates of the strings representing each statistic (hp, atk, def, sp.atk, sp.def, speed).
The table follows the following format [pointer][Y_cord][X_cord], with fields of 32bit, 16bit and 16bit respectively.
For convenience, i report the table i used:
EB F7 40 08 10 00 98 00 D9 F7 40 08 38 00 C8 00
E0 F7 40 08 58 00 C8 00 D5 F7 40 08 70 00 90 00
DD F7 40 08 58 00 58 00 E5 F7 40 08 38 00 58 00
Other important definitions are DEFAULT and ISTO.
The first definition, DEFAULT, allows you to choose which graphics apply to the menu.
If left at 0, the menu will have BW-style graphics, otherwise the standard ruby graphics will be loaded.
The second definition, ISTO, allows you to choose what will be the IV/EV display mode.
If left at 0, the display mode will be the graph with the expansion, otherwise the histogram will be loaded.
You can leave the other definitions unchanged.
Soon after, we find the declarations of all the prototypes of the functions that we are going to use.

From this point forward, there is the actual code of our implementation.
Inside the main the sprite of the first pokémon in the squad is displayed and the TIMER is set to 0.
Subsequently, the code of the second main is called.
The TIMER i mentioned earlier will be used in the second main to scan two different phases:
- the first, in which we will correct the sprite palette shown above.
- the second, in which there is the part of the code that will take care of the input of the keys.
By pressing the UP/DOWN, you can view each member of your team.
By pressing the RIGHT/LEFT, it's possible to change the data that will be displayed, changing from IV to EV or the other way around.
Finally, by pressing the B key, the menu will be closed.

Whenever one of the arrow keys is pressed, the information will be refreshed by calling the refreshInfo() function.
After printing the various menu strings, the data of the pokémon you are trying to view is read.
Subsequently, based on the values read, the points of the statistics graph are calculated.
These points are saved in a 2x7 matrix called "points".
The final additional element is used to have a "cyclical reference" when the matrix will be scanned by the for loop (the first and last points are equal).
To obtain the "expansion" effect of the graph, the code use a matrix of similar dimensions called progressPoints.
Using the for loop mentioned above, a temporary graph is drawn at each iteration.
This process will be repeated until all the points in the progressPoints matrix are the same as those contained in points (final graph).

The code will also load some graphic parts necessary for the correct display of the menu.
Inside the code there are the strings XXXXXX, YYYYYY, ZZZZZZ, KKKKKK, JJJJJJ.
These will be replaced with the offsets containing the data of the palettes, raws and tiles that you will use within the menu.
For convenience, i report the ones that i've used:
XXXXXX (palette BW style menu):
E0 03 00 00 08 21 10 42 A5 14 09 5F 8C 31 F7 5E E7 24 BD 77 E5 39 63 0C 5A 6B 4A 29 C6 18 00 00
YYYYYY (tile BW style menu):
10 00 09 00 20 11 11 D0 01 99 99 11 11 39 78 33 00 03 00 0B 10 03 B0 01 13 91 99 00 13 39 39 33 39 39 39 11 E1 20 03 D0 1F 10 12 39 91 13 91 00 1B 04 11 99 13 11 91 E0 5D 13 39 05 11 33 11 99 93 00 09 93 10 49 FF E0 3F F0 5F E0 5F 10 43 10 03 10 20 E0 A0 20 BF 7F 33 50 BC F0 3F 00 36 00 1D 70 01 F0 3F 80 FF FE 10 0B F0 3F 50 3F 10 82 F0 3F F0 01 90 01 33 00 33 33 33 C3 CC CC 3C C3 01 33 33 3C C3 43 34 3C D0 1F 00 44 44 44 44 34 33 33 43 00 34 44 44 43 34 14 41 43 EE F0 41 21 53 00 03 91 00 9B 10 03 D1 5F 91 40 99 00 03 33 99 99 93 13 99 38 93 93 E0 1F 01 99 00 C3 93 93 39 19 39 91 93 10 24 F0 3F 99 99 01 7D 22 33 13 00 DC 93 39 99 E0 DF 13 FE 00 74 00 03 10 57 50 03 C1 3D 10 7C 30 61 33 C0 00 03 F1 BF 99 13 93 93 33 39 2F 31 93 00 60 91 F0 5F 02 3B 00 95 00 9A 7E 93 00 20 00 C3 E0 7F 00 1D 10 59 01 83 91 1C 11 99 39 E2 5F 10 30 90 03 00 00 FE F0 01 90 01 F2 EF 00 0A 91 AF 51 E7 10 01 93 07 99 33 11 31 33 A0 93 02 E3 12 86 C0 00 89 12 CC 39 93 33 11 33 31 A3 B0 3F 39 01 45 39 33 33 12 B3 00 21 3D 33 13 B0 5F 52 E7 20 03 00 5F 33 C0 5F 37 39 93 13 4F 00 03 31 30 82 00 5F F1 52 F2 60 03 22 FB B0 20 01 4B 11 91 01 47 31 FF 30 C6 F0 3F 90 FB F0 3F 11 AC F0 3F 70 01 12 C3 FF 22 CB 02 D3 12 DB D0 9C 12 C3 22 CB 02 D3 12 DB DF F2 CE 12 C3 31 12 87 11 CB A1 3B 32 4F 02 0B 40 33 02 CB 13 91 13 33 13 31 FF F2 0F 02 69 01 20 10 03 11 7A E0 1F 22 CB 11 A0 FF 02 EC 01 7B E0 9F 52 C7 00 38 12 6C E0 7F 52 C7 B1 02 D7 93 00 3D E0 1F 11 91 33 00 7D FD 00 61 00 03 00 7E F1 9C 02 C3 00 A2 13 00 9E 7F 99 F0 1F 21 7E 15 2B 33 B0 F1 3F 62 C7 13 29 F3 F0 1D 11 1F F0 01 B0 01 C9 8C F0 03 70 03 0B BB BB BB BB 01 96 B4 F0 03 30 03 F2 40 1F F0 01 40 01 F0 1F 44 84 00 03 A8 A0 00 06 5A 00 06 85 BB 8B 5A 38 82 00 06 93 84 5A 38 99 00 06 99 AF 00 06 99 00 06 99 00 06 06 00 20 A0 01 5B FF F0 03 50 03 F0 93 E0 03 F0 01 B0 01 D0 2F A0 9F E0 00 06 F0 9F B0 9F 99 CC CC CC CC 00 77 77 77 77 88 88 88 88 F0 D0 5C F0 1F B0 6F 11 63 CC CC 7C 87 0E 77 77 37 83 40 1F F0 7F F0 7F 99 FF 10 2B 10 53 F1 CF 30 01 F0 1F F0 1F 10 73 41 47 95 00 07 39 58 00 06 A5 00 06 8A 00 06 41 B8 00 06 B4 83 A5 48 B4 10 BB FE F0 3D 70 01 10 BB F2 37 90 03 F0 5F 00 06 48 A0 00 06 44 00 06 44 58 8A BB BB 95 00 06 B4 8A 01 0B 48 F1 8B B4 00 26 57 44 00 1F 44 00 06 44 00 06 E0 01 12 E3 24 66 66 30 01 DD DD 30 01 22 22 9F 30 01 EE EE 30 01 92 8B 10 01 93 1F F5 F1 80 B0 01 00 00
KKKKKK (raw BW style menu):
A4 D2 A5 D2 A6 D2 A7 D2 A8 D2 A9 D2 AA D2 AB D2 AE D2 AE D2 AE D2 AE D2 AE D2 AF D2 AE D2 B0 D2 AE D2 AE D2 AE D2 AE D2 B1 D2 B2 D2 B3 D2 B4 D2 B5 D2 B6 D2 B7 D2 B8 D2 B9 D2 BA D2 BB D2 BB D2 BC D2 BD D2 BE D2 BF D2 C0 D2 C1 D2 C2 D2 C3 D2 AE D2 AE D2 AE D2 AE D2 AE D2 C6 D2 AE D2 C7 D2 AE D2 AE D2 AE D2 AE D2 C8 D2 C9 D2 CA D2 CB D2 CC D2 CD D2 CE D2 CF D2 D0 D2 D1 D2 BB D2 BB D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D3 D2 D4 D2 D5 D2 D4 D2 D6 D2 D7 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D8 D2 BB D2 BB D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D3 D2 D9 D2 DA D2 DB D2 DC D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D8 D2 BB D2 BB D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D3 D2 D4 D2 D6 D2 D7 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D8 D2 BB D2 BB D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D3 D2 DB D2 DC D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D8 D2 BB D2 BB D2 DD D2 DE D2 DD D2 DE D2 DD D2 DE D2 DD D2 DE D2 DF D2 D7 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D8 D2 BB D2 BB D2 DA D2 D9 D2 DA D2 D9 D2 DA D2 D9 D2 DA D2 DB D2 DC D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D8 D2 BB D2 BB D2 D5 D2 D4 D2 D5 D2 D4 D2 D5 D2 D4 D2 D6 D2 D7 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D8 D2 BB D2 BB D2 DA D2 D9 D2 DA D2 D9 D2 DA D2 DB D2 DC D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D8 D2 BB D2 BB D2 D5 D2 D4 D2 D5 D2 D4 D2 D6 D2 D7 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D8 D2 BB D2 BB D2 DA D2 D9 D2 DA D2 DB D2 DC D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D8 D2 BB D2 BB D2 D5 D2 D4 D2 D6 D2 D7 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D8 D2 BB D2 BB D2 DA D2 DB D2 DC D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D8 D2 BB D2 BB D2 D6 D2 D7 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D8 D2 BB D2 BB D2 E0 D2 E1 D2 E1 D2 E1 D2 E1 D2 E2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 E3 D2 BB D2 BB D2 E4 D2 E4 D2 E4 D2 E4 D2 E4 D2 E5 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 E6 D2 E7 D2 BB D2 BB D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D3 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 D2 E3 D2 E8 D2 D9 D2 BB D2 BB D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 E9 D2 BB D2 BB D2 EA D2 EA D2 EA D2 EA D2 EA D2 EA D2 EA D2 EA D2 EA D2 EA D2 EA D2 EA D2 EA D2 EA D2 EA D2 EA D2 EA D2 EA D2 EA D2 EA D2 EA D2 EA D2 EA D2 EA D2 EA D2 EA D2 EA D2 EA D2 EA D2 EA D2 BB D2 BB D2
JJJJJJ (raw upper bar BW style menu):
A4 D2 A5 D2 A6 D2 A7 D2 A8 D2 A9 D2 AC D2 AD D2 AE D2 AE D2 AE D2 AE D2 AE D2 B0 D2 AE D2 AF D2 AE D2 AE D2 AE D2 AE D2 B1 D2 B2 D2 B3 D2 B4 D2 B5 D2 B6 D2 B7 D2 B8 D2 B9 D2 BA D2 BB D2 BB D2 BC D2 BD D2 BE D2 BF D2 C0 D2 C1 D2 C4 D2 C5 D2 AE D2 AE D2 AE D2 AE D2 AE D2 C7 D2 AE D2 C6 D2 AE D2 AE D2 AE D2 AE D2 C8 D2 C9 D2 CA D2 CB D2 CC D2 CD D2 CE D2 CF D2 D0 D2 D1 D2 BB D2 BB D2
ZZZZZZ (tile bar histogram):
EE EE EE EE EE EE EE EE EE EE EE EE 88 88 88 88 EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE 88 88 88 88 11 EE EE EE 11 EE EE EE 11 EE EE EE 11 EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE 88 88 88 88 11 11 EE EE 11 11 EE EE 11 11 EE EE 11 11 EE EE EE EE EE EE EE EE EE EE EE EE EE EE 88 88 88 88 11 11 11 EE 11 11 11 EE 11 11 11 EE 11 11 11 EE EE EE EE EE EE EE EE EE EE EE EE EE 88 88 88 88 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE 8E EE EE EE 8E EE EE EE 8E EE EE EE 8E EE EE EE 8E

The topic has been published as "research", since i think the code can be improved.
In fact, the current version is very slow due to the numerous calculations that the CPU must perform to draw the lines of the graph.
Here is a video with the working implementation:
 
Última edición:

Samu

Usuario de Oro
I saw a video of a previous version of this system (which you were writing in ASM), and it looked cool. Firs of all, Imo swaping the system to C is just a win-win.

I was planning on doing something similar to this for a while. Actually, I was going to use the Pokenav code as a base, since one of its functions is really similar to this one (instead of showing EV/IV it shows the 'condition')

If you are having troubles with the execution speed of your code, i'd sugguest you to checkout the pokenav code in pokeruby (altough part of the code is still not documented/decompiled).

When I have some time I'll try to check your code, to see if I can suggest you any change to clean and speed up the code a little. Thank you for sharing this anyways, it's awesome.

(unfortunately, it'd be long until I have some time off, im currently working on something else :()
 

Andrea

Leyenda de WaH
Respuesta: Re: [RUBY] IV/EV Viewer

I saw a video of a previous version of this system (which you were writing in ASM), and it looked cool. Firs of all, Imo swaping the system to C is just a win-win.

I was planning on doing something similar to this for a while. Actually, I was going to use the Pokenav code as a base, since one of its functions is really similar to this one (instead of showing EV/IV it shows the 'condition')

If you are having troubles with the execution speed of your code, i'd sugguest you to checkout the pokenav code in pokeruby (altough part of the code is still not documented/decompiled).

When I have some time I'll try to check your code, to see if I can suggest you any change to clean and speed up the code a little. Thank you for sharing this anyways, it's awesome.

(unfortunately, it'd be long until I have some time off, im currently working on something else :()
Thanks so much for the tip!
Initially I had the same idea, but then i was a little discouraged by the fact that the pokenav had 5 "options" instead of 6.
If it were possible to use the functions already present in the ROM, it would be a great step forward!
 

Andrea

Leyenda de WaH
After working on code optimization, i was able to significantly speed up the expansion of the graph:

In addition, a new display mode has been developed, a sort of histogram:

Finally, the code has been structured so that you can decide which display mode and graphics use based on the value of a couple of definitions at the beginning of the file:
#define DEFAULT 0
#define ISTO 0
The first definition, DEFAULT, allows you to choose which graphics apply to the menu.
If left at 0, the menu will have BW-style graphics, otherwise the standard ruby graphics will be loaded.
The second definition, ISTO, allows you to choose what will be the IV/EV display mode.
If left at 0, the display mode will be the graph with the expansion, otherwise the histogram will be loaded.
The new version of the code will available in the next few days.
 

Andrea

Leyenda de WaH
Updated code in main post.
The new changes are the ones listed in the previous message.
 
Arriba