Skip to content

Instantly share code, notes, and snippets.

@selenologist
Created May 10, 2023 20:33
Show Gist options
  • Save selenologist/d4af998fb60a10c2222ea72d872e8b2d to your computer and use it in GitHub Desktop.
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
// 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
// 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
// $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