Created
May 10, 2023 20:33
-
-
Save selenologist/d4af998fb60a10c2222ea72d872e8b2d to your computer and use it in GitHub Desktop.
Reads a Pokemon Emerald save file and dumps the party's BoxPokemon structs to the screen as as QR code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// adapted from pret/pokeemerald | |
#ifndef GUARD_GBA_TYPES_H | |
#define GUARD_GBA_TYPES_H | |
#include <stdint.h> | |
typedef uint8_t u8; | |
typedef uint16_t u16; | |
typedef uint32_t u32; | |
typedef uint64_t u64; | |
typedef int8_t s8; | |
typedef int16_t s16; | |
typedef int32_t s32; | |
typedef int64_t s64; | |
typedef volatile u8 vu8; | |
typedef volatile u16 vu16; | |
typedef volatile u32 vu32; | |
typedef volatile u64 vu64; | |
typedef volatile s8 vs8; | |
typedef volatile s16 vs16; | |
typedef volatile s32 vs32; | |
typedef volatile s64 vs64; | |
typedef float f32; | |
typedef double f64; | |
typedef u8 bool8; | |
typedef u16 bool16; | |
typedef u32 bool32; | |
typedef vu8 vbool8; | |
typedef vu16 vbool16; | |
typedef vu32 vbool32; | |
#endif // GUARD_GBA_TYPES_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// adapted from pret/pokeemerald | |
#ifndef GUARD_POKEMON_H | |
#define GUARD_POKEMON_H | |
#define MAX_MON_MOVES 4 | |
#define POKEMON_NAME_LENGTH 10 | |
#define PLAYER_NAME_LENGTH 7 | |
struct PokemonSubstruct0 | |
{ | |
u16 species; | |
u16 heldItem; | |
u32 experience; | |
u8 ppBonuses; | |
u8 friendship; | |
u16 filler; | |
}; | |
struct PokemonSubstruct1 | |
{ | |
u16 moves[MAX_MON_MOVES]; | |
u8 pp[MAX_MON_MOVES]; | |
}; | |
struct PokemonSubstruct2 | |
{ | |
u8 hpEV; | |
u8 attackEV; | |
u8 defenseEV; | |
u8 speedEV; | |
u8 spAttackEV; | |
u8 spDefenseEV; | |
u8 cool; | |
u8 beauty; | |
u8 cute; | |
u8 smart; | |
u8 tough; | |
u8 sheen; | |
}; | |
struct PokemonSubstruct3 | |
{ | |
/* 0x00 */ u8 pokerus; | |
/* 0x01 */ u8 metLocation; | |
/* 0x02 */ u16 metLevel:7; | |
/* 0x02 */ u16 metGame:4; | |
/* 0x03 */ u16 pokeball:4; | |
/* 0x03 */ u16 otGender:1; | |
/* 0x04 */ u32 hpIV:5; | |
/* 0x04 */ u32 attackIV:5; | |
/* 0x05 */ u32 defenseIV:5; | |
/* 0x05 */ u32 speedIV:5; | |
/* 0x05 */ u32 spAttackIV:5; | |
/* 0x06 */ u32 spDefenseIV:5; | |
/* 0x07 */ u32 isEgg:1; | |
/* 0x07 */ u32 abilityNum:1; | |
/* 0x08 */ u32 coolRibbon:3; // Stores the highest contest rank achieved in the Cool category. | |
/* 0x08 */ u32 beautyRibbon:3; // Stores the highest contest rank achieved in the Beauty category. | |
/* 0x08 */ u32 cuteRibbon:3; // Stores the highest contest rank achieved in the Cute category. | |
/* 0x09 */ u32 smartRibbon:3; // Stores the highest contest rank achieved in the Smart category. | |
/* 0x09 */ u32 toughRibbon:3; // Stores the highest contest rank achieved in the Tough category. | |
/* 0x09 */ u32 championRibbon:1; // Given when defeating the Champion. Because both RSE and FRLG use it, later generations don't specify from which region it comes from. | |
/* 0x0A */ u32 winningRibbon:1; // Given at the Battle Tower's Level 50 challenge by winning a set of seven battles that extends the current streak to 56 or more. | |
/* 0x0A */ u32 victoryRibbon:1; // Given at the Battle Tower's Level 100 challenge by winning a set of seven battles that extends the current streak to 56 or more. | |
/* 0x0A */ u32 artistRibbon:1; // Given at the Contest Hall by winning a Master Rank contest with at least 800 points, and agreeing to have the Pokémon's portrait placed in the museum after being offered. | |
/* 0x0A */ u32 effortRibbon:1; // Given at Slateport's market to Pokémon with maximum EVs. | |
/* 0x0A */ u32 marineRibbon:1; // Never distributed. | |
/* 0x0A */ u32 landRibbon:1; // Never distributed. | |
/* 0x0A */ u32 skyRibbon:1; // Never distributed. | |
/* 0x0A */ u32 countryRibbon:1; // Distributed during Pokémon Festa '04 and '05 to tournament winners. | |
/* 0x0B */ u32 nationalRibbon:1; // Given to purified Shadow Pokémon in Colosseum/XD. | |
/* 0x0B */ u32 earthRibbon:1; // Given to teams that have beaten Mt. Battle's 100-battle challenge in Colosseum/XD. | |
/* 0x0B */ u32 worldRibbon:1; // Distributed during Pokémon Festa '04 and '05 to tournament winners. | |
/* 0x0B */ u32 unusedRibbons:4; // Discarded in Gen 4. | |
// The functionality of this bit changed in FRLG: | |
// In RS, this bit does nothing, is never set, & is accidentally unset when hatching Eggs. | |
// In FRLG & Emerald, this controls Mew & Deoxys obedience and whether they can be traded. | |
// If set, a Pokémon is a fateful encounter in FRLG's summary screen if hatched & for all Pokémon in Gen 4+ summary screens. | |
// Set for in-game event island legendaries, events distributed after a certain date, & Pokémon from XD: Gale of Darkness. | |
// Not to be confused with METLOC_FATEFUL_ENCOUNTER. | |
/* 0x0B */ u32 modernFatefulEncounter:1; | |
}; | |
#define max(a, b) ((a) >= (b) ? (a) : (b)) | |
// Number of bytes in the largest Pokémon substruct. | |
// They are assumed to be the same size, and will be padded to | |
// the largest size by the union. | |
// By default they are all 12 bytes. | |
#define NUM_SUBSTRUCT_BYTES (max(sizeof(struct PokemonSubstruct0), \ | |
max(sizeof(struct PokemonSubstruct1), \ | |
max(sizeof(struct PokemonSubstruct2), \ | |
sizeof(struct PokemonSubstruct3))))) | |
union PokemonSubstruct | |
{ | |
struct PokemonSubstruct0 type0; | |
struct PokemonSubstruct1 type1; | |
struct PokemonSubstruct2 type2; | |
struct PokemonSubstruct3 type3; | |
u16 raw[NUM_SUBSTRUCT_BYTES / 2]; // /2 because it's u16, not u8 | |
}; | |
struct BoxPokemon | |
{ | |
u32 personality; | |
u32 otId; | |
u8 nickname[POKEMON_NAME_LENGTH]; | |
u8 language; | |
u8 isBadEgg:1; | |
u8 hasSpecies:1; | |
u8 isEgg:1; | |
u8 unused:5; | |
u8 otName[PLAYER_NAME_LENGTH]; | |
u8 markings; | |
u16 checksum; | |
u16 unknown; | |
union | |
{ | |
u32 raw[(NUM_SUBSTRUCT_BYTES * 4) / 4]; // *4 because there are 4 substructs, /4 because it's u32, not u8 | |
union PokemonSubstruct substructs[4]; | |
} secure; | |
}; | |
struct Pokemon | |
{ | |
struct BoxPokemon box; | |
u32 status; | |
u8 level; | |
u8 mail; | |
u16 hp; | |
u16 maxHP; | |
u16 attack; | |
u16 defense; | |
u16 speed; | |
u16 spAttack; | |
u16 spDefense; | |
}; | |
#undef max | |
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// $CC qrparty.c -o qrparty -Os $(sdl-config --cflags --libs) -lqrencode | |
#include <stdio.h> | |
#include <fcntl.h> | |
#include <sys/mman.h> | |
#include <SDL.h> | |
#include <qrencode.h> | |
#include "gba_types.h" | |
#include "pokemon.h" | |
const char* charmap[256] = { | |
" " ,"À" ,"Á" ,"Â" ,"Ç" ,"È" ,"É" ,"Ê" ,"Ë" ,"Ì" ,"" ,"Î" ,"Ï" ,"Ò" ,"Ó" ,"Ô" , | |
"Œ" ,"Ù" ,"Ú" ,"Û" ,"Ñ" ,"ß" ,"à" ,"á" ,"" ,"ç" ,"è" ,"é" ,"ê" ,"ë" ,"ì" ,"" , | |
"î" ,"ï" ,"ò" ,"ó" ,"ô" ,"œ" ,"ù" ,"ú" ,"û" ,"ñ" ,"º" ,"ª" ,"ᵉ" ,"ʳ" ,"&" ,"+" , | |
"" ,"" ,"" ,"" ,"Lv","=" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" , | |
"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" , | |
"▯" ,"¿", "¡", "PK","MN","PO","Ké","BL","OC","K" ,"Í" ,"%" ,"(" ,")" ,"" ,"" , | |
"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"â" ,"" ,"" ,"" ,"" ,"" ,"" ,"â" , | |
"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"⬆" ,"⬇" ,"⬅" ,"➡ ","*" ,"*" ,"*" , | |
"*" ,"*" ,"*" ,"*" ,"ᵉ" ,"<" ,">" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" , | |
"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" , | |
"ʳᵉ","0" ,"1", "2", "3", "4", "5", "6", "7", "8", "9", "!", "?", ".", "-", "・", | |
"…" ,"“" ,"”", "‘", "’", "♂", "♀", "₽", ",", "×", "/", "A", "B", "C", "D", "E" , | |
"F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U" , | |
"V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k" , | |
"l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "▶" , | |
":", "Ä", "Ö", "Ü", "ä", "ö", "ü", "" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" , | |
}; | |
int main(int argc, char **argv){ | |
const char* path = "input.sav"; | |
if(argc >= 2){ | |
path = argv[1]; | |
} | |
int fd = open(path, O_RDONLY); | |
if(fd == -1){ | |
fprintf(stderr, "Cannot open %s\n", path); | |
return 1; | |
} | |
u8 *save = mmap(NULL, 0x20000, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0); | |
if(save == MAP_FAILED){ | |
perror("mmap"); | |
fprintf(stderr, "Failed to mmap %s\n", path); | |
return 1; | |
} | |
SDL_Event event; | |
/* Initialize the SDL library (starts the event loop) */ | |
if ( SDL_Init(SDL_INIT_VIDEO) < 0 ) { | |
fprintf(stderr, | |
"Couldn't initialize SDL: %s\n", SDL_GetError()); | |
return 1; | |
} | |
/* Clean up on exit, exit on window close and interrupt */ | |
atexit(SDL_Quit); | |
SDL_Surface *screen = SDL_SetVideoMode(320, 240, 16, SDL_HWSURFACE); | |
SDL_ShowCursor(0); | |
#define SECTOR_SIZE 0x1000 | |
#define SAVE_B_OFFSET 0xE000 | |
#define INDEX_OFFSET 0xFFC | |
// find whether save A or B is more recent | |
const u32 indexA = *(u32*) (save + INDEX_OFFSET); | |
const u32 indexB = *(u32*) (save + SAVE_B_OFFSET + INDEX_OFFSET); | |
size_t save_offset = SAVE_B_OFFSET; | |
if(indexA > indexB){ | |
printf("Save A is more recent\n"); | |
save_offset = 0; | |
} | |
else{ | |
printf("Save B is more recent\n"); | |
} | |
#define SECTORID_OFFSET 0xFF4 | |
#define SAVEBLOCK1_SECTORID 1 | |
for(int i = 0; i < SECTOR_SIZE * 14; i += SECTOR_SIZE){ | |
u16 sector_type = *(u16*) (save + save_offset + SECTORID_OFFSET + i); | |
if(sector_type == SAVEBLOCK1_SECTORID){ | |
save_offset += i; | |
break; | |
} | |
} | |
#define PARTY_OFFSET 0x238 // as per pokeemerald include/global.h | |
struct Pokemon *party = (struct Pokemon*) (save + save_offset + PARTY_OFFSET); | |
const size_t size = sizeof(struct Pokemon) * 6; | |
const Sint16 qr_x = 49; // (320 - (37*6)) / 2 | |
const Sint16 qr_y = 9; // (240 - (37*6)) / 2 | |
const Uint16 qr_wh = 6; // biggest that will fit | |
for(int i = 0; i < 6; ++i){ | |
if(party[i].box.isEgg){ | |
printf("EGG\n"); | |
continue; | |
} | |
for(int j = 0; j < POKEMON_NAME_LENGTH; ++j){ | |
u8 cur_char = party[i].box.nickname[j]; | |
if(!cur_char) break; | |
printf("%s", charmap[cur_char]); | |
} | |
printf("\n"); | |
// generate 37x37 (V5 binary = 84 bytes) qr code | |
QRcode *qr_code = QRcode_encodeData(sizeof(struct BoxPokemon), | |
(const unsigned char *) &party[i].box, | |
5, | |
QR_ECLEVEL_M); | |
if(!qr_code){ | |
perror("QRcode_encodeData"); | |
return 1; | |
} | |
SDL_FillRect(screen, NULL, 0xFFFF); | |
const Sint16 width = (Sint16)qr_code->width; | |
const Sint16 max_x = qr_x + width * qr_wh; | |
const Sint16 max_y = qr_y + width * qr_wh; | |
const unsigned char* qr_data = qr_code->data; | |
for(Sint16 y = qr_y; y < max_y; y += qr_wh){ | |
for(Sint16 x = qr_x; x < max_x; x+= qr_wh){ | |
// defaults to all white, so only fill black pixels | |
if((*qr_data) & 1){ | |
SDL_Rect dst = { | |
.x = x, | |
.y = y, | |
.w = qr_wh, | |
.h = qr_wh | |
}; | |
SDL_FillRect(screen, &dst, 0x0000); | |
} | |
qr_data++; | |
} | |
} | |
SDL_Flip(screen); | |
QRcode_free(qr_code); | |
while(SDL_WaitEvent(&event)){ | |
switch(event.type){ | |
case SDL_QUIT: | |
return 0; | |
case SDL_KEYUP: | |
goto next; | |
} | |
} | |
next: | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment