- 249
- Posts
- 12
- Years
- Seen Jun 17, 2024
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:
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:
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):
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:
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:
Spoiler:
Code:
#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:
Other important definitions are DEFAULT and ISTO.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
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:
Spoiler:
XXXXXX (palette BW style menu):
YYYYYY (tile 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
KKKKKK (raw 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
JJJJJJ (raw upper bar 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
ZZZZZZ (tile bar histogram):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
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:
Last edited: