first commit
This commit is contained in:
commit
104334eeb9
|
@ -0,0 +1,3 @@
|
|||
build
|
||||
*.gba
|
||||
*.elf
|
|
@ -0,0 +1,13 @@
|
|||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
|
@ -0,0 +1,285 @@
|
|||
#
|
||||
# Template tonc makefile
|
||||
#
|
||||
# Yoinked mostly from DKP's template
|
||||
#
|
||||
|
||||
# === SETUP ===========================================================
|
||||
|
||||
# --- No implicit rules ---
|
||||
.SUFFIXES:
|
||||
|
||||
# --- Paths ---
|
||||
export TONCLIB := ${DEVKITPRO}/libtonc
|
||||
|
||||
# === TONC RULES ======================================================
|
||||
#
|
||||
# Yes, this is almost, but not quite, completely like to
|
||||
# DKP's base_rules and gba_rules
|
||||
#
|
||||
|
||||
export PATH := $(DEVKITARM)/bin:$(DEVKITPRO)/tools/bin:$(PATH)
|
||||
|
||||
|
||||
# --- Executable names ---
|
||||
|
||||
PREFIX ?= arm-none-eabi-
|
||||
|
||||
export CC := $(PREFIX)gcc
|
||||
export CXX := $(PREFIX)g++
|
||||
export AS := $(PREFIX)as
|
||||
export AR := $(PREFIX)ar
|
||||
export NM := $(PREFIX)nm
|
||||
export OBJCOPY := $(PREFIX)objcopy
|
||||
|
||||
# LD defined in Makefile
|
||||
|
||||
|
||||
# === LINK / TRANSLATE ================================================
|
||||
|
||||
%.gba : %.elf
|
||||
@$(OBJCOPY) -O binary $< $@
|
||||
@echo built ... $(notdir $@)
|
||||
@gbafix $@ -t$(TITLE)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
%.mb.elf :
|
||||
@echo Linking multiboot
|
||||
$(LD) -specs=gba_mb.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
|
||||
$(NM) -Sn $@ > $(basename $(notdir $@)).map
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
%.elf :
|
||||
@echo Linking cartridge
|
||||
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
|
||||
$(NM) -Sn $@ > $(basename $(notdir $@)).map
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
%.a :
|
||||
@echo $(notdir $@)
|
||||
@rm -f $@
|
||||
$(AR) -crs $@ $^
|
||||
|
||||
|
||||
# === OBJECTIFY =======================================================
|
||||
|
||||
%.iwram.o : %.iwram.cpp
|
||||
@echo $(notdir $<)
|
||||
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(IARCH) -c $< -o $@
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
%.iwram.o : %.iwram.c
|
||||
@echo $(notdir $<)
|
||||
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) $(IARCH) -c $< -o $@
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
%.o : %.cpp
|
||||
@echo $(notdir $<)
|
||||
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(RARCH) -c $< -o $@
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
%.o : %.c
|
||||
@echo $(notdir $<)
|
||||
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) $(RARCH) -c $< -o $@
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
%.o : %.s
|
||||
@echo $(notdir $<)
|
||||
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
%.o : %.S
|
||||
@echo $(notdir $<)
|
||||
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# canned command sequence for binary data
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
define bin2o
|
||||
bin2s $< | $(AS) -o $(@)
|
||||
echo "extern const u8" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"_end[];" > `(echo $(<F) | tr . _)`.h
|
||||
echo "extern const u8" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(<F) | tr . _)`.h
|
||||
echo "extern const u32" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(<F) | tr . _)`.h
|
||||
endef
|
||||
# =====================================================================
|
||||
|
||||
# --- Main path ---
|
||||
|
||||
export PATH := $(DEVKITARM)/bin:$(PATH)
|
||||
|
||||
|
||||
# === PROJECT DETAILS =================================================
|
||||
# PROJ : Base project name
|
||||
# TITLE : Title for ROM header (12 characters)
|
||||
# LIBS : Libraries to use, formatted as list for linker flags
|
||||
# BUILD : Directory for build process temporaries. Should NOT be empty!
|
||||
# SRCDIRS : List of source file directories
|
||||
# DATADIRS : List of data file directories
|
||||
# INCDIRS : List of header file directories
|
||||
# LIBDIRS : List of library directories
|
||||
# General note: use `.' for the current dir, don't leave the lists empty.
|
||||
|
||||
export PROJ ?= $(notdir $(CURDIR))
|
||||
TITLE := $(PROJ)
|
||||
|
||||
LIBS := -ltonc
|
||||
|
||||
BUILD := build
|
||||
SRCDIRS := src lib
|
||||
DATADIRS := data
|
||||
INCDIRS := src
|
||||
LIBDIRS := $(TONCLIB)
|
||||
|
||||
# --- switches ---
|
||||
|
||||
bMB := 1 # Multiboot build
|
||||
bTEMPS := 0 # Save gcc temporaries (.i and .s files)
|
||||
bDEBUG2 := 0 # Generate debug info (bDEBUG2? Not a full DEBUG flag. Yet)
|
||||
|
||||
|
||||
# === BUILD FLAGS =====================================================
|
||||
# This is probably where you can stop editing
|
||||
# NOTE: I've noticed that -fgcse and -ftree-loop-optimize sometimes muck
|
||||
# up things (gcse seems fond of building masks inside a loop instead of
|
||||
# outside them for example). Removing them sometimes helps
|
||||
|
||||
# --- Architecture ---
|
||||
|
||||
ARCH := -mthumb-interwork -mthumb
|
||||
RARCH := -mthumb-interwork -mthumb
|
||||
IARCH := -mthumb-interwork -marm -mlong-calls
|
||||
|
||||
# --- Main flags ---
|
||||
|
||||
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -O2
|
||||
CFLAGS += -Wall
|
||||
CFLAGS += $(INCLUDE)
|
||||
CFLAGS += -ffast-math -fno-strict-aliasing
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
|
||||
|
||||
ASFLAGS := $(ARCH) $(INCLUDE)
|
||||
LDFLAGS := $(ARCH) -Wl,-Map,$(PROJ).map
|
||||
|
||||
# --- switched additions ----------------------------------------------
|
||||
|
||||
# --- Multiboot ? ---
|
||||
ifeq ($(strip $(bMB)), 1)
|
||||
TARGET := $(PROJ).mb
|
||||
else
|
||||
TARGET := $(PROJ)
|
||||
CXXFLAGS += -DNOMULTIBOOT
|
||||
endif
|
||||
|
||||
# --- Save temporary files ? ---
|
||||
ifeq ($(strip $(bTEMPS)), 1)
|
||||
CFLAGS += -save-temps
|
||||
CXXFLAGS += -save-temps
|
||||
endif
|
||||
|
||||
# --- Debug info ? ---
|
||||
|
||||
ifeq ($(strip $(bDEBUG)), 1)
|
||||
CFLAGS += -DDEBUG -g
|
||||
CXXFLAGS += -DDEBUG -g
|
||||
ASFLAGS += -DDEBUG -g
|
||||
LDFLAGS += -g
|
||||
else
|
||||
CFLAGS += -DNDEBUG
|
||||
CXXFLAGS += -DNDEBUG
|
||||
ASFLAGS += -DNDEBUG
|
||||
endif
|
||||
|
||||
|
||||
# === BUILD PROC ======================================================
|
||||
|
||||
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
||||
|
||||
# Still in main dir:
|
||||
# * Define/export some extra variables
|
||||
# * Invoke this file again from the build dir
|
||||
# PONDER: what happens if BUILD == "" ?
|
||||
|
||||
export OUTPUT := $(CURDIR)/$(TARGET)
|
||||
export VPATH := \
|
||||
$(foreach dir, $(SRCDIRS) , $(CURDIR)/$(dir)) \
|
||||
$(foreach dir, $(DATADIRS), $(CURDIR)/$(dir))
|
||||
|
||||
export DEPSDIR := $(CURDIR)/$(BUILD)
|
||||
|
||||
# --- List source and data files ---
|
||||
|
||||
CFILES := $(foreach dir, $(SRCDIRS) , $(notdir $(wildcard $(dir)/*.c)))
|
||||
CPPFILES := $(foreach dir, $(SRCDIRS) , $(notdir $(wildcard $(dir)/*.cpp)))
|
||||
SFILES := $(foreach dir, $(SRCDIRS) , $(notdir $(wildcard $(dir)/*.s)))
|
||||
BINFILES := $(foreach dir, $(DATADIRS), $(notdir $(wildcard $(dir)/*.*)))
|
||||
|
||||
# --- Set linker depending on C++ file existence ---
|
||||
ifeq ($(strip $(CPPFILES)),)
|
||||
export LD := $(CC)
|
||||
else
|
||||
export LD := $(CXX)
|
||||
endif
|
||||
|
||||
# --- Define object file list ---
|
||||
export OFILES := $(addsuffix .o, $(BINFILES)) \
|
||||
$(CFILES:.c=.o) $(CPPFILES:.cpp=.o) \
|
||||
$(SFILES:.s=.o)
|
||||
|
||||
# --- Create include and library search paths ---
|
||||
export INCLUDE := $(foreach dir,$(INCDIRS),-I$(CURDIR)/$(dir)) \
|
||||
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
|
||||
-I$(CURDIR)/$(BUILD)
|
||||
|
||||
export LIBPATHS := -L$(CURDIR) $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
|
||||
|
||||
# --- Create BUILD if necessary, and run this makefile from there ---
|
||||
|
||||
$(BUILD):
|
||||
@[ -d $@ ] || mkdir -p $@
|
||||
@make --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
|
||||
arm-none-eabi-nm -Sn $(OUTPUT).elf > $(BUILD)/$(TARGET).map
|
||||
|
||||
all : $(BUILD)
|
||||
|
||||
clean:
|
||||
@echo clean ...
|
||||
@rm -rf $(BUILD) $(TARGET).elf $(TARGET).gba $(TARGET).sav
|
||||
|
||||
|
||||
else # If we're here, we should be in the BUILD dir
|
||||
|
||||
DEPENDS := $(OFILES:.o=.d)
|
||||
|
||||
# --- Main targets ----
|
||||
|
||||
$(OUTPUT).gba : $(OUTPUT).elf
|
||||
|
||||
$(OUTPUT).elf : $(OFILES)
|
||||
|
||||
-include $(DEPENDS)
|
||||
|
||||
|
||||
endif # End BUILD switch
|
||||
|
||||
# --- More targets ----------------------------------------------------
|
||||
|
||||
.PHONY: clean rebuild start
|
||||
|
||||
rebuild: clean $(BUILD)
|
||||
|
||||
start:
|
||||
start "$(TARGET).gba"
|
||||
|
||||
restart: rebuild start
|
||||
|
||||
# EOF
|
|
@ -0,0 +1,15 @@
|
|||
# pokemon stat view
|
||||
|
||||
GBA homebrew for checking the stats of pokemon in the current party of a gen 3 save file, by inserting the cartridge you want to check.
|
||||
|
||||

|
||||
|
||||
To use, run `pokemon_stat_view.mb.gba` via multiboot or a flashcart. Once it's loaded, it continuously checks the cartridge slot to see if it contains a generation 3 pokemon game. If it does, the screen is refreshed with the details of that save file's party. Simply tear out your current cartridge (if applicable) and insert the game you want to check. You can keep swapping in new cartridges for as long as the software stays running. (Hot tip: If you're loading it via multiboot, you can have the game you want to check already inserted, and then press Start+Select during startup to cancel booting to cartridge ROM.)
|
||||
|
||||
**Be sure to back up any precious save files.** This software *shouldn't* cause any problems for your save files (as it doesn't perform any writes beyond a few magic bytes for bank switching), and it didn't hurt any of mine during any of my testing, but hotswapping GBA cartridges is highly cursed. God only knows what kind of devine punishment one invites upon themselves by accepting this forbidden technique into their lives. Please do not come to me to say that it ruined your childhood save file; I can't fix it and it will make me sad.
|
||||
|
||||
# building
|
||||
|
||||
Should just be able to run `make clean; make build` to get it going. Flip the `bMB` flag in the makefile from 1 to 0 to get a non-multiboot build, for use in testing with an emulator (where you can supply any save file you like). This removes the code that repeatedly tests for the existence of a Pokemon cartridge, instead loading the save file once and then sitting there forever. I bet there's a way to set up make so it builds this rom with a different command, instead of making you edit a flag in the makefile, but I'll be damned if I know how to do it.
|
||||
|
||||
I'm not exactly a C++ developer by day, which you can also tell because the code is the way it is.
|
|
@ -0,0 +1,375 @@
|
|||
#include <tonc.h>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
|
||||
void log(std::string text) {
|
||||
tte_write("#{es; P:0,0}");
|
||||
tte_write(text.c_str());
|
||||
}
|
||||
|
||||
void init() {
|
||||
REG_DISPCNT= DCNT_MODE0 | DCNT_BG0;
|
||||
tte_init_se_default(0, BG_CBB(0) | BG_SBB(31));
|
||||
}
|
||||
|
||||
int get_int_from_sram(int offset) {
|
||||
// I don't know why just reading an int gives me weird values
|
||||
// They're close to if it was reading in big endian, but not exactly
|
||||
// e.g. 0x000001DA -> 3671775962
|
||||
// But whatever
|
||||
|
||||
// NOTE FROM FUTURE HOLLY: Is it because the port width for sram is only one byte?
|
||||
// Whatever, I've decided I don't care
|
||||
int out = 0;
|
||||
for (int i=3; i>=0; i--) {
|
||||
out += (*((char*)(MEM_SRAM + offset + i)) << (i*8));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
int get_save_game_offset() {
|
||||
// Get offset to the more recent of the two saved games (other is a backup)
|
||||
int a = get_int_from_sram(0x0FFC);
|
||||
int b = get_int_from_sram(0xEFFC);
|
||||
if (a > b) {
|
||||
return 0x0000;
|
||||
}
|
||||
return 0xE000;
|
||||
}
|
||||
|
||||
int data_offset(int personality_value, u8 structure) {
|
||||
// the order of the four substructures in a pokemon's "data"
|
||||
// structure is determined by the pokemon's personality value
|
||||
// mod 24
|
||||
u16 structure_orders[24] = {
|
||||
0x0123,
|
||||
0x0132,
|
||||
0x0213,
|
||||
0x0231,
|
||||
0x0312,
|
||||
0x0321,
|
||||
0x1023,
|
||||
0x1032,
|
||||
0x1203,
|
||||
0x1230,
|
||||
0x1302,
|
||||
0x1320,
|
||||
0x2013,
|
||||
0x2031,
|
||||
0x2103,
|
||||
0x2130,
|
||||
0x2301,
|
||||
0x2310,
|
||||
0x3012,
|
||||
0x3021,
|
||||
0x3102,
|
||||
0x3120,
|
||||
0x3201,
|
||||
0x3210,
|
||||
};
|
||||
|
||||
u16 this_order = structure_orders[(u32)(personality_value) % 24];
|
||||
|
||||
for (int i=0; i<4; i++) {
|
||||
if (((this_order >> (12 - i*4)) & 0xf) == structure) {
|
||||
return i*12;
|
||||
}
|
||||
}
|
||||
// will never happen unless i really mess this up
|
||||
return -1;
|
||||
}
|
||||
|
||||
void add_num(int num, std::string* stats) {
|
||||
// Write a number to stats, right-justifying it to 3 columns
|
||||
if (num < 100) {
|
||||
*stats += " ";
|
||||
}
|
||||
if (num < 10) {
|
||||
*stats += " ";
|
||||
}
|
||||
*stats += std::to_string(num);
|
||||
}
|
||||
|
||||
void add_num2(int num, std::string* stats) {
|
||||
// Write a number to stats, right-justifying it to 2 columns
|
||||
if (num < 10) {
|
||||
*stats += " ";
|
||||
}
|
||||
*stats += std::to_string(num);
|
||||
}
|
||||
|
||||
char get_nature_mod(char nature, char stat) {
|
||||
// '+' if stat is boosed by nature, '-' if it's decreased, ' ' if neutral
|
||||
int up[5] = {
|
||||
0b0000000000000000000011110, // atk
|
||||
0b0000000000000001110100000, // def
|
||||
0b0000010111000000000000000, // spa
|
||||
0b0111100000000000000000000, // spd
|
||||
0b0000000000110110000000000, // spe
|
||||
};
|
||||
int down[5] = {
|
||||
0b0000100001000010000100000, // atk
|
||||
0b0001000010000100000000010, // def
|
||||
0b0100000000010000100001000, // spa
|
||||
0b0000010000100001000010000, // spd
|
||||
0b0010000100000000010000100, // spe
|
||||
};
|
||||
|
||||
u8 index = stat; // oh my god g++ shut UP
|
||||
if ((up[index] >> nature) & 1) {
|
||||
return '+';
|
||||
}
|
||||
|
||||
if ((down[index] >> nature) & 1) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
return ' ';
|
||||
}
|
||||
|
||||
void interpret_pokemon(int offset, std::string* stats) {
|
||||
// Add info about the pokemon's stats to the string
|
||||
|
||||
char names[445][8] = {"??????", "Bulbas", "Ivysau", "Venusa", "Charma", "Charme", "Chariz", "Squirt", "Wartor", "Blasto", "Caterp", "Metapo", "Butter", "Weedle", "Kakuna", "Beedri", "Pidgey", "Pidgeo", "Pidgeo", "Rattat", "Ratica", "Spearo", "Fearow", "Ekans", "Arbok", "Pikach", "Raichu", "Sandsh", "Sandsl", "Nidora", "Nidori", "Nidoqu", "Nidora", "Nidori", "Nidoki", "Clefai", "Clefab", "Vulpix", "Nineta", "Jiggly", "Wiggly", "Zubat", "Golbat", "Oddish", "Gloom", "Vilepl", "Paras", "Parase", "Venona", "Venomo", "Diglet", "Dugtri", "Meowth", "Persia", "Psyduc", "Golduc", "Mankey", "Primea", "Growli", "Arcani", "Poliwa", "Poliwh", "Poliwr", "Abra", "Kadabr", "Alakaz", "Machop", "Machok", "Macham", "Bellsp", "Weepin", "Victre", "Tentac", "Tentac", "Geodud", "Gravel", "Golem", "Ponyta", "Rapida", "Slowpo", "Slowbr", "Magnem", "Magnet", "Farfet", "Doduo", "Dodrio", "Seel", "Dewgon", "Grimer", "Muk", "Shelld", "Cloyst", "Gastly", "Haunte", "Gengar", "Onix", "Drowze", "Hypno", "Krabby", "Kingle", "Voltor", "Electr", "Exeggc", "Exeggu", "Cubone", "Marowa", "Hitmon", "Hitmon", "Lickit", "Koffin", "Weezin", "Rhyhor", "Rhydon", "Chanse", "Tangel", "Kangas", "Horsea", "Seadra", "Goldee", "Seakin", "Staryu", "Starmi", "Mr.", "Mime", "Scythe", "Jynx", "Electa", "Magmar", "Pinsir", "Tauros", "Magika", "Gyarad", "Lapras", "Ditto", "Eevee", "Vapore", "Jolteo", "Flareo", "Porygo", "Omanyt", "Omasta", "Kabuto", "Kabuto", "Aeroda", "Snorla", "Articu", "Zapdos", "Moltre", "Dratin", "Dragon", "Dragon", "Mewtwo", "Mew", "Chikor", "Baylee", "Megani", "Cyndaq", "Quilav", "Typhlo", "Totodi", "Crocon", "Ferali", "Sentre", "Furret", "Hootho", "Noctow", "Ledyba", "Ledian", "Spinar", "Ariado", "Crobat", "Chinch", "Lantur", "Pichu", "Cleffa", "Igglyb", "Togepi", "Togeti", "Natu", "Xatu", "Mareep", "Flaaff", "Amphar", "Bellos", "Marill", "Azumar", "Sudowo", "Polito", "Hoppip", "Skiplo", "Jumplu", "Aipom", "Sunker", "Sunflo", "Yanma", "Wooper", "Quagsi", "Espeon", "Umbreo", "Murkro", "Slowki", "Misdre", "Unown", "Wobbuf", "Girafa", "Pineco", "Forret", "Dunspa", "Gligar", "Steeli", "Snubbu", "Granbu", "Qwilfi", "Scizor", "Shuckl", "Heracr", "Snease", "Teddiu", "Ursari", "Slugma", "Magcar", "Swinub", "Pilosw", "Corsol", "Remora", "Octill", "Delibi", "Mantin", "Skarmo", "Houndo", "Houndo", "Kingdr", "Phanpy", "Donpha", "Porygo", "Stantl", "Smearg", "Tyrogu", "Hitmon", "Smooch", "Elekid", "Magby", "Miltan", "Blisse", "Raikou", "Entei", "Suicun", "Larvit", "Pupita", "Tyrani", "Lugia", "Ho-Oh", "Celebi", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "Treeck", "Grovyl", "Scepti", "Torchi", "Combus", "Blazik", "Mudkip", "Marsht", "Swampe", "Poochy", "Mighty", "Zigzag", "Linoon", "Wurmpl", "Silcoo", "Beauti", "Cascoo", "Dustox", "Lotad", "Lombre", "Ludico", "Seedot", "Nuzlea", "Shiftr", "Nincad", "Ninjas", "Shedin", "Taillo", "Swello", "Shroom", "Breloo", "Spinda", "Wingul", "Pelipp", "Surski", "Masque", "Wailme", "Wailor", "Skitty", "Delcat", "Kecleo", "Baltoy", "Claydo", "Nosepa", "Torkoa", "Sabley", "Barboa", "Whisca", "Luvdis", "Corphi", "Crawda", "Feebas", "Miloti", "Carvan", "Sharpe", "Trapin", "Vibrav", "Flygon", "Makuhi", "Hariya", "Electr", "Manect", "Numel", "Cameru", "Spheal", "Sealeo", "Walrei", "Cacnea", "Cactur", "Snorun", "Glalie", "Lunato", "Solroc", "Azuril", "Spoink", "Grumpi", "Plusle", "Minun", "Mawile", "Mediti", "Medich", "Swablu", "Altari", "Wynaut", "Duskul", "Dusclo", "Roseli", "Slakot", "Vigoro", "Slakin", "Gulpin", "Swalot", "Tropiu", "Whismu", "Loudre", "Explou", "Clampe", "Huntai", "Goreby", "Absol", "Shuppe", "Banett", "Sevipe", "Zangoo", "Relica", "Aron", "Lairon", "Aggron", "Castfo", "Volbea", "Illumi", "Lileep", "Cradil", "Anorit", "Armald", "Ralts", "Kirlia", "Gardev", "Bagon", "Shelgo", "Salame", "Beldum", "Metang", "Metagr", "Regiro", "Regice", "Regist", "Kyogre", "Groudo", "Rayqua", "Latias", "Latios", "Jirach", "Deoxys", "Chimec", "Pokémo", "Egg", "Unown", "Unown", "Unown", "Unown", "Unown", "Unown", "Unown", "Unown", "Unown", "Unown", "Unown", "Unown", "Unown", "Unown", "Unown", "Unown", "Unown", "Unown", "Unown", "Unown", "Unown", "Unown", "Unown", "Unown", "Unown", "Unown", "Unown", "??????", "Bonsly", "Munchl"};
|
||||
int personality_value = get_int_from_sram(offset);
|
||||
int ot_id = get_int_from_sram(offset+4);
|
||||
int evs = data_offset(personality_value, 2);
|
||||
int ivs = data_offset(personality_value, 3) + 4;
|
||||
|
||||
int species_id = (get_int_from_sram(offset+32+data_offset(personality_value, 0)) ^ ot_id ^ personality_value) & 0xffff;
|
||||
|
||||
int hpatk = get_int_from_sram(offset+88);
|
||||
int atk = hpatk >> 0x10;
|
||||
int hp = hpatk & 0xff;
|
||||
int defspe = get_int_from_sram(offset+92);
|
||||
int spe = defspe >> 0x10;
|
||||
int def = defspe & 0xff;
|
||||
int spaspd = get_int_from_sram(offset+96);
|
||||
int spd = spaspd >> 0x10;
|
||||
int spa = spaspd & 0xff;
|
||||
|
||||
char nature = ((u32)(personality_value)) % 25;
|
||||
|
||||
// all these places where a value is XORed with the OT ID and the personality
|
||||
// value are because the *whole "data" structure* is repeatedly XORed with them.
|
||||
// i could have done the "decryption" somewhere else but every value i need out
|
||||
// of the structure happens to be aligned to four bytes, the length of the key,
|
||||
// so fuck it basically
|
||||
int iv_offset = offset+32+ivs;
|
||||
u32 iv = (u32)(get_int_from_sram(iv_offset) ^ ot_id ^ personality_value);
|
||||
int hp_iv = iv & 31;
|
||||
int atk_iv = (iv >> 5) & 31;
|
||||
int def_iv = (iv >> 10) & 31;
|
||||
int spe_iv = (iv >> 15) & 31;
|
||||
int spa_iv = (iv >> 20) & 31;
|
||||
int spd_iv = (iv >> 25) & 31;
|
||||
|
||||
int ev_offset = offset+32+evs;
|
||||
u32 ev1 = (u32)(get_int_from_sram(ev_offset) ^ ot_id ^ personality_value);
|
||||
int hp_ev = ev1 & 0xf;
|
||||
int atk_ev = (ev1 >> 8) & 0xf;
|
||||
int def_ev = (ev1 >> 16) & 0xf;
|
||||
int spe_ev = (ev1 >> 24) & 0xf;
|
||||
u32 ev2 = (u32)(get_int_from_sram(ev_offset + 4) ^ ot_id ^ personality_value);
|
||||
int spa_ev = ev2 & 0xf;
|
||||
int spd_ev = (ev2 >> 8) & 0xf;
|
||||
|
||||
// this is how you're supposed to write c++ right?
|
||||
char* name = names[species_id + 1];
|
||||
const char* padding = " " + strlen(name);
|
||||
*stats += padding;
|
||||
*stats += name;
|
||||
*stats += " ";
|
||||
//add_num(species_id, stats); *stats += " ";
|
||||
add_num(hp, stats); *stats += " ";
|
||||
add_num(atk, stats); *stats += " ";
|
||||
add_num(def, stats); *stats += " ";
|
||||
add_num(spa, stats); *stats += " ";
|
||||
add_num(spd, stats); *stats += " ";
|
||||
add_num(spe, stats); *stats += "\n";
|
||||
|
||||
*stats += "Nat/IV ";
|
||||
add_num(hp_iv, stats); *stats += " ";
|
||||
*stats += get_nature_mod(nature, 0); add_num2(atk_iv, stats); *stats += " ";
|
||||
*stats += get_nature_mod(nature, 1); add_num2(def_iv, stats); *stats += " ";
|
||||
*stats += get_nature_mod(nature, 2); add_num2(spa_iv, stats); *stats += " ";
|
||||
*stats += get_nature_mod(nature, 3); add_num2(spd_iv, stats); *stats += " ";
|
||||
*stats += get_nature_mod(nature, 4); add_num2(spe_iv, stats); *stats += "\n";
|
||||
|
||||
*stats += " EV ";
|
||||
add_num(hp_ev, stats); *stats += " ";
|
||||
add_num(atk_ev, stats); *stats += " ";
|
||||
add_num(def_ev, stats); *stats += " ";
|
||||
add_num(spa_ev, stats); *stats += " ";
|
||||
add_num(spd_ev, stats); *stats += " ";
|
||||
add_num(spe_ev, stats); *stats += "\n";
|
||||
}
|
||||
|
||||
u64 get_section_offsets(int save_game) {
|
||||
// Find party/items section and trainer info section, since these move each time the game is saved
|
||||
|
||||
// Once upon a time this returned the offset to both sections packed into a u64, but that caused
|
||||
// problems with bank switching. Since I only need four bytes out of the trainer info section, I
|
||||
// instead just grabbed those bytes when I knew I'd be able to and packed them into the return
|
||||
// value. I have not yet thought of a better name for the function.
|
||||
int section_num = 0;
|
||||
int sectionid;
|
||||
u32 section;
|
||||
int sections_found = 0;
|
||||
u64 section_offsets = 0;
|
||||
|
||||
bool bank_switched = false;
|
||||
bool switch_back = false;
|
||||
|
||||
while (sections_found != 2) {
|
||||
section = (0x1000 * section_num++);
|
||||
sectionid = *(u8*)(MEM_SRAM + (bank_switched ? 0x0000 : save_game) + section + 0x0FF4);
|
||||
|
||||
if (sectionid == 0) {
|
||||
// We actually only need one word out of this section
|
||||
// Might as well just grab it now
|
||||
section_offsets |= ((u64)(get_int_from_sram(section + 0x00AC)) << 32);
|
||||
sections_found++;
|
||||
} else if (sectionid == 1) {
|
||||
section_offsets |= (section & 0xffffffff) + (bank_switched ? 0x0000 : save_game);
|
||||
sections_found++;
|
||||
|
||||
// If we haven't yet switched banks, but we do later, we need to know to switch
|
||||
// back so this pointer still makes sense
|
||||
switch_back = !bank_switched;
|
||||
}
|
||||
|
||||
if (section_num == 2 && save_game && !bank_switched) {
|
||||
// If we're using save game B and the sections we need don't
|
||||
// happen to be sections zero and one, then they won't fit in
|
||||
// the GBA's 64KiB SRAM address space
|
||||
|
||||
// ∧_∧
|
||||
// (。・ω・。)つ━☆・*。 I cast Spell of Switch Banks!
|
||||
// ⊂ / ・゜+.
|
||||
// しーJ °。+
|
||||
|
||||
*((vu8*)(MEM_SRAM + 0x5555)) = 0xAA;
|
||||
*((vu8*)(MEM_SRAM + 0x2AAA)) = 0x55;
|
||||
*((vu8*)(MEM_SRAM + 0x5555)) = 0xB0;
|
||||
*((vu8*)(MEM_SRAM + 0x0000)) = 0x01;
|
||||
bank_switched = true;
|
||||
|
||||
// This also means that we have to start reading from the top
|
||||
// of MEM_SRAM again
|
||||
section_num = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (switch_back) {
|
||||
// If we found the party in the first two sections of save game
|
||||
// B, but the trainer info was found in a later section, that
|
||||
// means that the pointer to the party section is no longer right.
|
||||
|
||||
// ∧_∧
|
||||
// (。・ω・。)つ━☆・*。 I cast Spell of Switch Banks Again!
|
||||
// ⊂ / ・゜+.
|
||||
// しーJ °。+
|
||||
|
||||
*((vu8*)(MEM_SRAM + 0x5555)) = 0xAA;
|
||||
*((vu8*)(MEM_SRAM + 0x2AAA)) = 0x55;
|
||||
*((vu8*)(MEM_SRAM + 0x5555)) = 0xB0;
|
||||
*((vu8*)(MEM_SRAM + 0x0000)) = 0x00;
|
||||
}
|
||||
|
||||
return section_offsets;
|
||||
}
|
||||
|
||||
void get_cartridge_name(char* cart_name) {
|
||||
memcpy(cart_name, (char*)(MEM_ROM + 0x00A0), 15);
|
||||
cart_name[15] = '\0'; // don't think i need to do this but just in case
|
||||
}
|
||||
|
||||
bool valid_cartridge(char* this_cart_name) {
|
||||
const char* cart_names[5] = {
|
||||
"POKEMON RUBYAXV",
|
||||
"POKEMON SAPPAXP",
|
||||
"POKEMON LEAFBPG",
|
||||
"POKEMON FIREBPR",
|
||||
"POKEMON EMERBPE",
|
||||
};
|
||||
|
||||
for (int i=0; i<5; i++) {
|
||||
if (!strcmp(this_cart_name, cart_names[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void get_stats(std::string* stats) {
|
||||
*stats = "";
|
||||
int save_game = get_save_game_offset();
|
||||
|
||||
u64 sectionoffsets = get_section_offsets(save_game);
|
||||
int game_version = sectionoffsets >> 32;
|
||||
int team_items_section = sectionoffsets & 0xffffffff;
|
||||
|
||||
int version_offset;
|
||||
// RSE and FRLG have almost identical save structures, but some stuff is shifted around
|
||||
// Importantly for us, we need to know the offset into the Team / Items section that the
|
||||
// party information is found
|
||||
|
||||
// Getting this from the cartridge header would be easier and prevent some nonsense, but
|
||||
// I've decided to be a sicko who wants to support the case where someone restored an RSE
|
||||
// save onto a FRLG cart or something
|
||||
if (game_version == 1) {
|
||||
// frlg
|
||||
version_offset = 0x0000;
|
||||
} else {
|
||||
// rse
|
||||
version_offset = 0x0200;
|
||||
}
|
||||
|
||||
int partySize = get_int_from_sram(team_items_section + version_offset + 0x0034);
|
||||
|
||||
*stats += " HP ATK DEF SPA SPD SPE\n";
|
||||
*stats += " --- --- --- --- --- ---\n";
|
||||
for (int i=0; i<partySize; i++) {
|
||||
interpret_pokemon(team_items_section + version_offset + 0x0038 + (i * 100), stats);
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
init();
|
||||
|
||||
// double negative here so my ide doesn't grey out the more important code
|
||||
// vsc isn't smart enough to see defines that are added by the makefile :pensive:
|
||||
#ifndef NOMULTIBOOT
|
||||
std::string output = "Insert a gen 3 Pokemon\ncartridge";
|
||||
log(output);
|
||||
|
||||
char cart_names[3][16];
|
||||
|
||||
while (true) {
|
||||
get_cartridge_name(cart_names[0]);
|
||||
// only read cart if it matches the name that was read last frame, but not the
|
||||
// frame before (to ensure the cartridge is good and seated, but also new)
|
||||
if (!strcmp(cart_names[0], cart_names[1]) && strcmp(cart_names[0], cart_names[2]) && valid_cartridge(cart_names[0])) {
|
||||
get_stats(&output);
|
||||
log(output);
|
||||
}
|
||||
|
||||
strcpy(cart_names[2], cart_names[1]);
|
||||
strcpy(cart_names[1], cart_names[0]);
|
||||
vid_vsync();
|
||||
}
|
||||
#else
|
||||
// if i'm compiling without multiboot, it's for debugging in an emulator
|
||||
// i want it to run even though the cartridge header is wrong, because the
|
||||
// save file will be one i've put there manually
|
||||
std::string output;
|
||||
get_stats(&output);
|
||||
log(output);
|
||||
while (true) {
|
||||
vid_vsync();
|
||||
};
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue