From d4e309bb19b74c4b21ca19952c9b1cdd067c667a Mon Sep 17 00:00:00 2001 From: slipstream/RoL Date: Sun, 19 Feb 2017 01:19:35 +0000 Subject: Initial commit of the fork Forking gba-link-cable-dumper to gba-gen3multiboot --- LICENSE | 1 + Makefile.gc | 2 +- Makefile.wii | 2 +- README.md | 16 +- build.bat | 3 +- gba/Makefile | 13 +- gba/gba_pkjb.ld | 296 +++++++++++++++++++++++++++ gba/gba_pkjb.specs | 8 + gba/source/main.c | 262 +++--------------------- gba/start/pkjb_crt0.s | 98 +++++++++ source/main.c | 551 ++++++++++++++------------------------------------ 11 files changed, 616 insertions(+), 636 deletions(-) create mode 100644 gba/gba_pkjb.ld create mode 100644 gba/gba_pkjb.specs create mode 100644 gba/start/pkjb_crt0.s diff --git a/LICENSE b/LICENSE index bd6e929..3bef6d6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,6 @@ The MIT License (MIT) +Copyright (c) 2017 slipstream/RoL Copyright (c) 2016 FIX94 Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/Makefile.gc b/Makefile.gc index 431c094..3fddc9b 100644 --- a/Makefile.gc +++ b/Makefile.gc @@ -15,7 +15,7 @@ include $(DEVKITPPC)/gamecube_rules # SOURCES is a list of directories containing source code # INCLUDES is a list of directories containing extra header files #--------------------------------------------------------------------------------- -TARGET := linkcabledump_gc +TARGET := gen3multiboot_gc BUILD := build SOURCES := source DATA := data diff --git a/Makefile.wii b/Makefile.wii index 4de7f71..15afd9d 100644 --- a/Makefile.wii +++ b/Makefile.wii @@ -15,7 +15,7 @@ include $(DEVKITPPC)/wii_rules # SOURCES is a list of directories containing source code # INCLUDES is a list of directories containing extra header files #--------------------------------------------------------------------------------- -TARGET := linkcabledump_wii +TARGET := gen3multiboot_wii BUILD := build SOURCES := source DATA := data diff --git a/README.md b/README.md index 91d29d0..4483a83 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,12 @@ -# GBA Link Cable Dumper -A GC and Wii Homebrew App to get GBA BIOS, ROMs and saves via the GC GBA Link Cable. -Save Support based on SendSave by Chishm. -GBA BIOS Dumper by Dark Fader. +# GBA Gen3 Multiboot +A GC and Wii homebrew app that sends a binary to the GBA using the different multiboot protocol used by the third generation of Pokémon games (Ruby, Sapphire, Emerald, FireRed, LeafGreen). # Usage -Just have a GC Controller in Port 1 and a GBA without a game inserted or aborted game launch by holding select+start in Port 2. -The bin, gba and sav files dumped will be placed in a folder called "dumps" on your main device (SD Gecko on gamecube and SD/USB on Wii). Please note that dumping GBA ROMs can take a long time (32mb takes about 48 minutes) because of the cable protocol limitations, a estimation will be displayed on screen before you dump it as a reference. +Have a GC Controller in Port 1 and a GBA with Gen3 game in Port 2. +Put your payload code to do some interesting stuff with the Pokémon game you have in `gba` dir. Example code that changes first character of player name to 'z' on Pokémon Ruby English (v1.0-1.2) provided. +Recompile, send to Wii or GC, turn on your GBA, hope that your code runs after the initial copyright screen of the game. +(Code execution rate is for some reason not 100% reliable, PR to fix would be greatly appreciated. Sometimes KeyC derivation fails, sometimes GBA ignores the sent multiboot image, could be due to failure of a few different sends) + +# Acknowledgements +Thanks to FIX94 for your multiboot game dumper, which the multiboot code is loosely based on (differences in crypto & protocol...) +Without it, this would have taken longer to do than the 2 days or so that it took. \ No newline at end of file diff --git a/build.bat b/build.bat index aece78f..8a35180 100644 --- a/build.bat +++ b/build.bat @@ -1,8 +1,9 @@ +@echo off cd gba make clean make cd .. -mv -f gba/gba_mb.gba data/gba_mb.gba +mv -f gba/gba_pkjb.gba data/gba_mb.gba make -f Makefile.gc clean make -f Makefile.gc make -f Makefile.wii clean diff --git a/gba/Makefile b/gba/Makefile index 99dfbb6..f9cb296 100644 --- a/gba/Makefile +++ b/gba/Makefile @@ -7,6 +7,10 @@ ifeq ($(strip $(DEVKITARM)),) $(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM) endif +%_pkjb.elf: + @echo linking pkjb + @$(LD) -specs=../gba_pkjb.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + include $(DEVKITARM)/gba_rules #--------------------------------------------------------------------------------- @@ -16,7 +20,7 @@ include $(DEVKITARM)/gba_rules # DATA is a list of directories containing data files # INCLUDES is a list of directories containing header files #--------------------------------------------------------------------------------- -TARGET := $(shell basename $(CURDIR))_mb +TARGET := $(shell basename $(CURDIR))_pkjb BUILD := build SOURCES := source DATA := @@ -38,8 +42,8 @@ CFLAGS += $(INCLUDE) CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -ASFLAGS := $(ARCH) -LDFLAGS = -g $(ARCH) -Wl,-Map,$(notdir $@).map +ASFLAGS := -g $(ARCH) +LDFLAGS = $(ARCH) -Wl,-Map,$(notdir $@).map #--------------------------------------------------------------------------------- # any extra libraries we wish to link with the project @@ -109,6 +113,7 @@ export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) #--------------------------------------------------------------------------------- $(BUILD): @[ -d $@ ] || mkdir -p $@ + $(CC) -MMD -MP -MF start/pkjb_crt0.d -x assembler-with-cpp $(ASFLAGS) -c start/pkjb_crt0.s -o start/pkjb_crt0.o $(ERROR_FILTER) @make --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile all : $(BUILD) @@ -150,7 +155,7 @@ $(OUTPUT).elf : $(OFILES) #--------------------------------------------------------------------------------- @echo $(notdir $<) @$(bin2o) - + #--------------------------------------------------------------------------------- # This rule creates assembly source files using grit # grit takes an image file and a .grit describing how the file is to be processed diff --git a/gba/gba_pkjb.ld b/gba/gba_pkjb.ld new file mode 100644 index 0000000..102cb42 --- /dev/null +++ b/gba/gba_pkjb.ld @@ -0,0 +1,296 @@ +/* Linker Script Original v1.3 by Jeff Frohwein */ +/* v1.0 - Original release */ +/* v1.1 - Added proper .data section support */ +/* v1.2 - Added support for c++ & iwram overlays */ +/* - Major contributions by Jason Wilkins. */ +/* v1.3 - .ewram section now can be used when */ +/* compiling for MULTIBOOT mode. This fixes */ +/* malloc() in DevKitAdvance which depends */ +/* on __eheap_start instead of end to define*/ +/* the starting location of heap space. */ +/* External global variable __gba_iwram_heap*/ +/* support added to allow labels end, _end, */ +/* & __end__ to point to end of iwram or */ +/* the end of ewram. */ +/* Additions by WinterMute */ +/* v1.4 - .sbss section added for unitialised */ +/* data in ewram */ +/* v1.5 - padding section added to stop EZF */ +/* stripping important data */ +/* v1.6 - added memory sections */ + +/* This file is released into the public domain */ +/* for commercial or non-commercial use with no */ +/* restrictions placed upon it. */ + +/* NOTE!!!: This linker script defines the RAM & */ +/* ROM start addresses. In order for it to work */ +/* properly, remove -Ttext and -Tbss linker */ +/* options from your makefile if they are */ +/* present. */ + +/* You can use the following to view section */ +/* addresses in your .elf file: */ +/* objdump -h file.elf */ +/* Please note that empty sections may incorrectly*/ +/* list the lma address as the vma address for */ +/* some versions of objdump. */ + +OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm") +OUTPUT_ARCH(arm) +ENTRY(_start) + +MEMORY { + + rom : ORIGIN = 0x08000000, LENGTH = 32M + iwram : ORIGIN = 0x03000000, LENGTH = 32K + ewram : ORIGIN = 0x02000000, LENGTH = 256K +} + + + +__text_start = ORIGIN(ewram); +__eheap_end = ORIGIN(ewram) + LENGTH(ewram); +__iwram_start = ORIGIN(iwram); +__iwram_top = ORIGIN(iwram) + LENGTH(iwram);; + +__sp_irq = __iwram_top - 0x060; +__sp_usr = __sp_irq - 0x0a0; +__irq_flags = 0x03007ff8; + +SECTIONS +{ + . = __text_start; + .init : + { + KEEP (*(.init)) + . = ALIGN(4); + } >ewram =0xff + + .plt : + { + *(.plt) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >ewram + + .text ALIGN (4): + { + *(EXCLUDE_FILE (*.iwram*) .text) + *(.text .stub .text.* .gnu.linkonce.t.*) + KEEP (*(.text.*personality*)) + /* .gnu.warning sections are handled specially by elf32.em. */ + *(.gnu.warning) + *(.glue_7t) *(.glue_7) *(.vfp11_veneer) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >ewram = 0xff + + __text_end = .; + .fini : + { + KEEP (*(.fini)) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >ewram =0 + + .rodata : + { + *(.rodata) + *all.rodata*(*) + *(.roda) + *(.rodata.*) + *(.gnu.linkonce.r*) + SORT(CONSTRUCTORS) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >ewram = 0xff + + .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >ewram + __exidx_start = .; + .ARM.exidx : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) } >ewram + __exidx_end = .; + /* Ensure the __preinit_array_start label is properly aligned. We + could instead move the label definition inside the section, but + the linker would then create the section even if it turns out to + be empty, which isn't pretty. */ + . = ALIGN(32 / 8); + PROVIDE (__preinit_array_start = .); + .preinit_array : { KEEP (*(.preinit_array)) } >ewram = 0xff + PROVIDE (__preinit_array_end = .); + PROVIDE (__init_array_start = .); + .init_array : { KEEP (*(.init_array)) } >ewram = 0xff + PROVIDE (__init_array_end = .); + PROVIDE (__fini_array_start = .); + .fini_array : { KEEP (*(.fini_array)) } >ewram = 0xff + PROVIDE (__fini_array_end = .); + .ctors : + { + /* gcc uses crtbegin.o to find the start of the constructors, so + we make sure it is first. Because this is a wildcard, it + doesn't matter if the user does not actually link against + crtbegin.o; the linker won't look for a file to match a + wildcard. The wildcard also means that it doesn't matter which + directory crtbegin.o is in. */ + KEEP (*crtbegin.o(.ctors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >ewram = 0 + + .dtors : + { + KEEP (*crtbegin.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >ewram = 0 + + .jcr : { KEEP (*(.jcr)) } >ewram + .eh_frame : + { + KEEP (*(.eh_frame)) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >ewram = 0 + + .gcc_except_table : + { + *(.gcc_except_table) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >ewram = 0 + + __iwram_lma = .; + + .iwram __iwram_start : AT (__iwram_lma) + { + __iwram_start__ = ABSOLUTE(.) ; + *(.iwram) + *iwram.*(.text) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + __iwram_end__ = ABSOLUTE(.) ; + } >iwram = 0xff + + __data_lma = __iwram_lma + SIZEOF(.iwram) ; + + .bss ALIGN(4) (NOLOAD): + { + __bss_start__ = ABSOLUTE(.); + *(.dynbss) + *(.gnu.linkonce.b*) + *(.bss*) + *(COMMON) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + __bss_end__ = ABSOLUTE(.) ; + } + + .data ALIGN(4) : AT (__data_lma) + { + __data_start__ = ABSOLUTE(.); + *(.data) + *(.data.*) + *(.gnu.linkonce.d*) + CONSTRUCTORS + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + __data_end__ = ABSOLUTE(.); + } >iwram = 0xff + + __iwram_overlay_lma = __data_lma + SIZEOF(.data); + + PROVIDE (edata = .); + __iwram_overlay_start = . ; + + OVERLAY ALIGN(4) : NOCROSSREFS AT (__iwram_overlay_lma) + { + .iwram0 { *(.iwram0) . = ALIGN(4);} + .iwram1 { *(.iwram1) . = ALIGN(4);} + .iwram2 { *(.iwram2) . = ALIGN(4);} + .iwram3 { *(.iwram3) . = ALIGN(4);} + .iwram4 { *(.iwram4) . = ALIGN(4);} + .iwram5 { *(.iwram5) . = ALIGN(4);} + .iwram6 { *(.iwram6) . = ALIGN(4);} + .iwram7 { *(.iwram7) . = ALIGN(4);} + .iwram8 { *(.iwram8) . = ALIGN(4);} + .iwram9 { *(.iwram9) . = ALIGN(4);} + } >iwram = 0xff + + __ewram_lma = LOADADDR(.iwram0) + SIZEOF(.iwram0)+SIZEOF(.iwram1)+SIZEOF(.iwram2)+SIZEOF(.iwram3)+SIZEOF(.iwram4)+SIZEOF(.iwram5)+SIZEOF(.iwram6)+SIZEOF(.iwram7)+SIZEOF(.iwram8)+SIZEOF(.iwram9); + + __iwram_overlay_end = __ewram_lma ; + + /* v1.3 */ + __ewram_start = __ewram_lma ; + + .ewram __ewram_start : AT (__ewram_lma) + { + *(.ewram) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + __ewram_end = ABSOLUTE(.); + } >ewram = 0xff + + __ewram_overlay_lma = __ewram_lma + SIZEOF(.ewram); + + .sbss ALIGN(4)(NOLOAD): + { + __sbss_start__ = ABSOLUTE(.); + *(.sbss) + . = ALIGN(4); + __sbss_end__ = ABSOLUTE(.); + __end__ = ABSOLUTE(.); + __eheap_start = ABSOLUTE(.); + } + + OVERLAY ALIGN(4): NOCROSSREFS AT (__ewram_overlay_lma) + { + .ewram0 { *(.ewram0) . = ALIGN(4);} + .ewram1 { *(.ewram1) . = ALIGN(4);} + .ewram2 { *(.ewram2) . = ALIGN(4);} + .ewram3 { *(.ewram3) . = ALIGN(4);} + .ewram4 { *(.ewram4) . = ALIGN(4);} + .ewram5 { *(.ewram5) . = ALIGN(4);} + .ewram6 { *(.ewram6) . = ALIGN(4);} + .ewram7 { *(.ewram7) . = ALIGN(4);} + .ewram8 { *(.ewram8) . = ALIGN(4);} + .ewram9 { *(.ewram9) . = ALIGN(4);} + } >ewram = 0xff + __ewram_overlay_end = ABSOLUTE(.); + + __eheap_start = __ewram_overlay_end ; + + _end = __ewram_overlay_end; + __end__ = __ewram_overlay_end; + __rom_end__ = __ewram_overlay_end; + + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + .stack 0x80000 : { _stack = .; *(.stack) } + /* These must appear regardless of . */ +} diff --git a/gba/gba_pkjb.specs b/gba/gba_pkjb.specs new file mode 100644 index 0000000..7c9027b --- /dev/null +++ b/gba/gba_pkjb.specs @@ -0,0 +1,8 @@ +%rename link old_link + +*link: +-T ../gba_pkjb.ld%s %(old_link) --gc-sections + +*startfile: +../start/pkjb_crt0%O%s crti%O%s crtbegin%O%s + diff --git a/gba/source/main.c b/gba/source/main.c index ee94c35..ce6969b 100644 --- a/gba/source/main.c +++ b/gba/source/main.c @@ -1,241 +1,45 @@ /* - * Copyright (C) 2016 FIX94 + * Example Gen3-multiboot payload by slipstream/RoL 2017. + * Supports only English Ruby, v1.0-1.2. * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ #include -#include -#include -#include "libSave.h" -#define REG_WAITCNT *(vu16 *)(REG_BASE + 0x204) -#define JOY_WRITE 2 -#define JOY_READ 4 -#define JOY_RW 6 - -u8 save_data[0x20000] __attribute__ ((section (".sbss"))); - -s32 getGameSize(void) -{ - if(*(vu32*)(0x08000004) != 0x51AEFF24) - return -1; - s32 i; - for(i = (1<<20); i < (1<<25); i<<=1) - { - vu16 *rompos = (vu16*)(0x08000000+i); - int j; - bool romend = true; - for(j = 0; j < 0x1000; j++) - { - if(rompos[j] != j) - { - romend = false; - break; - } - } - if(romend) break; - } - return i; -} - -//--------------------------------------------------------------------------------- -// Program entry point -//--------------------------------------------------------------------------------- int main(void) { -//--------------------------------------------------------------------------------- - - // the vblank interrupt must be enabled for VBlankIntrWait() to work - // since the default dispatcher handles the bios flags no vblank handler - // is required - irqInit(); - irqEnable(IRQ_VBLANK); - - consoleDemoInit(); - REG_JOYTR = 0; - // ansi escape sequence to set print co-ordinates - // /x1b[line;columnH - u32 i; - iprintf("\x1b[9;2HGBA Link Cable Dumper v1.6\n"); - iprintf("\x1b[10;4HPlease look at the TV\n"); - // disable this, needs power - SNDSTAT = 0; - SNDBIAS = 0; - // Set up waitstates for EEPROM access etc. - REG_WAITCNT = 0x0317; - //clear out previous messages - REG_HS_CTRL |= JOY_RW; - while (1) { - if(REG_HS_CTRL&JOY_READ) - { - REG_HS_CTRL |= JOY_RW; - s32 gamesize = getGameSize(); - u32 savesize = SaveSize(save_data,gamesize); - REG_JOYTR = gamesize; - //wait for a cmd receive for safety - while((REG_HS_CTRL&JOY_WRITE) == 0) ; - REG_HS_CTRL |= JOY_RW; - REG_JOYTR = savesize; - //wait for a cmd receive for safety - while((REG_HS_CTRL&JOY_WRITE) == 0) ; - REG_HS_CTRL |= JOY_RW; - if(gamesize == -1) - { - REG_JOYTR = 0; - continue; //nothing to read - } - //game in, send header - for(i = 0; i < 0xC0; i+=4) - { - REG_JOYTR = *(vu32*)(0x08000000+i); - while((REG_HS_CTRL&JOY_READ) == 0) ; - REG_HS_CTRL |= JOY_RW; - } - REG_JOYTR = 0; - //wait for other side to choose - while((REG_HS_CTRL&JOY_WRITE) == 0) ; - REG_HS_CTRL |= JOY_RW; - u32 choseval = REG_JOYRE; - if(choseval == 0) - { - REG_JOYTR = 0; - continue; //nothing to read - } - else if(choseval == 1) - { - //disable interrupts - u32 prevIrqMask = REG_IME; - REG_IME = 0; - //dump the game - for(i = 0; i < gamesize; i+=4) - { - REG_JOYTR = *(vu32*)(0x08000000+i); - while((REG_HS_CTRL&JOY_READ) == 0) ; - REG_HS_CTRL |= JOY_RW; - } - //restore interrupts - REG_IME = prevIrqMask; - } - else if(choseval == 2) - { - //disable interrupts - u32 prevIrqMask = REG_IME; - REG_IME = 0; - //backup save - switch (savesize){ - case 0x200: - GetSave_EEPROM_512B(save_data); - break; - case 0x2000: - GetSave_EEPROM_8KB(save_data); - break; - case 0x8000: - GetSave_SRAM_32KB(save_data); - break; - case 0x10000: - GetSave_FLASH_64KB(save_data); - break; - case 0x20000: - GetSave_FLASH_128KB(save_data); - break; - default: - break; - } - //restore interrupts - REG_IME = prevIrqMask; - //say gc side we read it - REG_JOYTR = savesize; - //wait for a cmd receive for safety - while((REG_HS_CTRL&JOY_WRITE) == 0) ; - REG_HS_CTRL |= JOY_RW; - //send the save - for(i = 0; i < savesize; i+=4) - { - REG_JOYTR = *(vu32*)(save_data+i); - while((REG_HS_CTRL&JOY_READ) == 0) ; - REG_HS_CTRL |= JOY_RW; - } - } - else if(choseval == 3 || choseval == 4) - { - REG_JOYTR = savesize; - if(choseval == 3) - { - //receive the save - for(i = 0; i < savesize; i+=4) - { - while((REG_HS_CTRL&JOY_WRITE) == 0) ; - REG_HS_CTRL |= JOY_RW; - *(vu32*)(save_data+i) = REG_JOYRE; - } - } - else - { - //clear the save - for(i = 0; i < savesize; i+=4) - *(vu32*)(save_data+i) = 0; - } - //disable interrupts - u32 prevIrqMask = REG_IME; - REG_IME = 0; - //write it - switch (savesize){ - case 0x200: - PutSave_EEPROM_512B(save_data); - break; - case 0x2000: - PutSave_EEPROM_8KB(save_data); - break; - case 0x8000: - PutSave_SRAM_32KB(save_data); - break; - case 0x10000: - PutSave_FLASH_64KB(save_data); - break; - case 0x20000: - PutSave_FLASH_128KB(save_data); - break; - default: - break; - } - //restore interrupts - REG_IME = prevIrqMask; - //say gc side we're done - REG_JOYTR = 0; - //wait for a cmd receive for safety - while((REG_HS_CTRL&JOY_WRITE) == 0) ; - REG_HS_CTRL |= JOY_RW; - } - REG_JOYTR = 0; - } - else if(REG_HS_CTRL&JOY_WRITE) - { - REG_HS_CTRL |= JOY_RW; - u32 choseval = REG_JOYRE; - if(choseval == 5) - { - //disable interrupts - u32 prevIrqMask = REG_IME; - REG_IME = 0; - //dump BIOS - for (i = 0; i < 0x4000; i+=4) - { - // the lower bits are inaccurate, so just get it four times :) - u32 a = MidiKey2Freq((WaveData *)(i-4), 180-12, 0) * 2; - u32 b = MidiKey2Freq((WaveData *)(i-3), 180-12, 0) * 2; - u32 c = MidiKey2Freq((WaveData *)(i-2), 180-12, 0) * 2; - u32 d = MidiKey2Freq((WaveData *)(i-1), 180-12, 0) * 2; - REG_JOYTR = ((a>>24<<24) | (d>>24<<16) | (c>>24<<8) | (b>>24)); - while((REG_HS_CTRL&JOY_READ) == 0) ; - REG_HS_CTRL |= JOY_RW; - } - //restore interrupts - REG_IME = prevIrqMask; - } - REG_JOYTR = 0; - } - Halt(); + // check the ROM code, make sure this game is supported. + char* ROM = 0x8000000; + + if ((*(u32*)(&ROM[0xAC])) != 'EVXA') return 0; // Pokémon Ruby english, nothing else supported! + + void(*loadsave)(char a1); + // get the address of the save loading function. + switch (ROM[0xBC]) { // version number + case 0: + loadsave = 0x8125EC9; + break; + case 1: + case 2: + loadsave = 0x8125EE9; + break; + default: + return 0; //bail out } + loadsave(0); + // now the save is loaded, we can do what we want with the loaded blocks. + // here as a small PoC, changing first letter of player name to 'z'. + u8* gSaveBlock2 = 0x2024EA4; + gSaveBlock2[0] = 0xee; // 'z' + // Now we've done what we want, time to return to the game. + // Can't just return, the game will reload the save. + // So let's just call the main-loop directly ;) + void(*mainloop)() = 0x80002A5; + // turn the sound back on before we head back to the game + *(vu16 *)(REG_BASE + 0x84) = 0x8f; + mainloop(); + // Anything past here will not be executed. + return 0; } diff --git a/gba/start/pkjb_crt0.s b/gba/start/pkjb_crt0.s new file mode 100644 index 0000000..1bf5bd7 --- /dev/null +++ b/gba/start/pkjb_crt0.s @@ -0,0 +1,98 @@ + .section ".init" + .global _start + .align + .arm +@--------------------------------------------------------------------------------- +_start: +@--------------------------------------------------------------------------------- + b rom_header_end + + .fill 156,1,0 @ Nintendo Logo Character Data (8000004h) + .fill 16,1,0 @ Game Title + .byte 0x30,0x31 @ Maker Code (80000B0h) + .byte 0x96 @ Fixed Value (80000B2h) + .byte 0x00 @ Main Unit Code (80000B3h) + .byte 0x00 @ Device Type (80000B4h) + .fill 7,1,0 @ unused + .byte 0x00 @ Software Version No (80000BCh) + .byte 0xf0 @ Complement Check (80000BDh) + .byte 0x00,0x00 @ Checksum (80000BEh) + +@--------------------------------------------------------------------------------- +rom_header_end: +@--------------------------------------------------------------------------------- + b start_vector @ This branch must be here for proper + @ positioning of the following header. + + .GLOBAL __boot_method, __slave_number +@--------------------------------------------------------------------------------- +__boot_method: +@--------------------------------------------------------------------------------- + .byte 0 @ boot method (0=ROM boot, 3=Multiplay boot) +@--------------------------------------------------------------------------------- +__slave_number: +@--------------------------------------------------------------------------------- + .byte 0 @ slave # (1=slave#1, 2=slave#2, 3=slave#3) + + .byte 0 @ reserved + .byte 0 @ reserved + .word 0 @ reserved + .word 0 @ reserved + .word 0 @ reserved + .word 0 @ reserved + .word 0 @ reserved + .word 0 @ reserved + + .fill 4096,1,0 @ 4kb of filler so no useful code gets overwritten when flash bytes get copied over the top. + .global start_vector + .align +@--------------------------------------------------------------------------------- +start_vector: +@--------------------------------------------------------------------------------- + +@--------------------------------------------------------------------------------- +@ Enter Thumb mode +@--------------------------------------------------------------------------------- + add r0, pc, #1 + bx r0 + + .thumb +@ Turn off sound + ldr r1, =0x4000084 + eor r0, r0, r0 + strh r0, [r1] + +@--------------------------------------------------------------------------------- +@ set heap end +@--------------------------------------------------------------------------------- + ldr r1, =fake_heap_end + ldr r0, =__eheap_end + str r0, [r1] +@--------------------------------------------------------------------------------- +@ global constructors +@--------------------------------------------------------------------------------- + ldr r3, =__libc_init_array + push {lr} + bl _blx_r3_stub +@--------------------------------------------------------------------------------- +@ Jump to user code +@--------------------------------------------------------------------------------- + mov r0, #0 @ int argc + mov r1, #0 @ char *argv[] + ldr r3, =main + bl _blx_r3_stub +@; If we're here, turn the sound back on before we return + ldr r1, =0x4000084 + mov r0, #0x8F + strh r0, [r1] + pop {pc} + +@--------------------------------------------------------------------------------- +_blx_r3_stub: +@--------------------------------------------------------------------------------- + bx r3 + + .align + .pool + .end + diff --git a/source/main.c b/source/main.c index 7fd5683..ad34eff 100644 --- a/source/main.c +++ b/source/main.c @@ -1,4 +1,5 @@ /* + * Copyright (c) 2017 slipstream/RoL * Copyright (C) 2016 FIX94 * * This software may be modified and distributed under the terms @@ -13,8 +14,6 @@ #include #include #include -#include -#include //from my tests 50us seems to be the lowest //safe si transfer delay in between calls @@ -27,9 +26,8 @@ void printmain() { printf("\x1b[2J"); printf("\x1b[37m"); - printf("GBA Link Cable Dumper v1.6 by FIX94\n"); - printf("Save Support based on SendSave by Chishm\n"); - printf("GBA BIOS Dumper by Dark Fader\n \n"); + printf("Pokemon Gen 3 GBA Multiboot PoC by slipstream/RoL\n"); + printf("Based on GBA Link Cable Dumper by FIX94\n\n"); } u8 *resbuf,*cmdbuf; @@ -46,21 +44,17 @@ void acb(s32 res, u32 val) resval = val; } -unsigned int docrc(u32 crc, u32 val) -{ - int i; - for(i = 0; i < 0x20; i++) - { - if((crc^val)&1) - { - crc>>=1; - crc^=0xa1c1; - } - else - crc>>=1; - val>>=1; +unsigned int docrc(u32 crc,u32 val) { + u32 result; + + result = val ^ crc; + for (int i = 0; i < 0x20; i++) { + if (result & 1) { + result >>= 1; + result ^= 0xA1C1; + } else result >>= 1; } - return crc; + return result; } void endproc() @@ -70,64 +64,7 @@ void endproc() VIDEO_WaitVSync(); exit(0); } -void fixFName(char *str) -{ - u8 i = 0; - for(i = 0; i < strlen(str); ++i) - { - if(str[i] < 0x20 || str[i] > 0x7F) - str[i] = '_'; - else switch(str[i]) - { - case '\\': - case '/': - case ':': - case '*': - case '?': - case '\"': - case '<': - case '>': - case '|': - str[i] = '_'; - break; - default: - break; - } - } -} -unsigned int calckey(unsigned int size) -{ - unsigned int ret = 0; - size=(size-0x200) >> 3; - int res1 = (size&0x3F80) << 1; - res1 |= (size&0x4000) << 2; - res1 |= (size&0x7F); - res1 |= 0x380000; - int res2 = res1; - res1 = res2 >> 0x10; - int res3 = res2 >> 8; - res3 += res1; - res3 += res2; - res3 <<= 24; - res3 |= res2; - res3 |= 0x80808080; - if((res3&0x200) == 0) - { - ret |= (((res3)&0xFF)^0x4B)<<24; - ret |= (((res3>>8)&0xFF)^0x61)<<16; - ret |= (((res3>>16)&0xFF)^0x77)<<8; - ret |= (((res3>>24)&0xFF)^0x61); - } - else - { - ret |= (((res3)&0xFF)^0x73)<<24; - ret |= (((res3>>8)&0xFF)^0x65)<<16; - ret |= (((res3>>16)&0xFF)^0x64)<<8; - ret |= (((res3>>24)&0xFF)^0x6F); - } - return ret; -} void doreset() { cmdbuf[0] = 0xFF; //reset @@ -135,6 +72,7 @@ void doreset() SI_Transfer(1,cmdbuf,1,resbuf,3,transcb,SI_TRANS_DELAY); while(transval == 0) ; } + void getstatus() { cmdbuf[0] = 0; //status @@ -142,6 +80,7 @@ void getstatus() SI_Transfer(1,cmdbuf,1,resbuf,3,transcb,SI_TRANS_DELAY); while(transval == 0) ; } + u32 recv() { memset(resbuf,0,32); @@ -151,6 +90,7 @@ u32 recv() while(transval == 0) ; return *(vu32*)resbuf; } + void send(u32 msg) { cmdbuf[0]=0x15;cmdbuf[1]=(msg>>0)&0xFF;cmdbuf[2]=(msg>>8)&0xFF; @@ -160,26 +100,7 @@ void send(u32 msg) SI_Transfer(1,cmdbuf,5,resbuf,1,transcb,SI_TRANS_DELAY); while(transval == 0) ; } -bool dirExists(const char *path) -{ - DIR *dir; - dir = opendir(path); - if(dir) - { - closedir(dir); - return true; - } - return false; -} -void createFile(const char *path, size_t size) -{ - int fd = open(path, O_WRONLY|O_CREAT); - if(fd >= 0) - { - ftruncate(fd, size); - close(fd); - } -} + void warnError(char *msg) { puts(msg); @@ -195,6 +116,68 @@ void fatalError(char *msg) sleep(5); exit(0); } + +u32 genKeyA() { + u32 retries = 0; + while (true) { + u32 key = 0; + if (retries > 32) { + key = 0xDD654321; + } else { + key = (rand() & 0x00ffffff) | 0xDD000000; + } + u32 unk = (key % 2 != 0); + u32 v12 = key; + for (u32 v13 = 1; v13 < 32; v13++) { + v12 >>= 1; + unk += (v12 % 2 != 0); + } + if ((unk >= 10 && unk <= 24)) { + if (retries > 4) printf("KeyA retries = %d",retries); + printf("KeyA = 0x%08x\n",key); + return key; + } + retries++; + } +} + +u32 checkKeyB(u32 KeyBRaw) { + if ((KeyBRaw & 0xFF) != 0xEE) { + printf("Invalid KeyB - lowest 8 bits should be 0xEE, actually 0x%02x\n",((u8)(KeyBRaw))); + return 0; + } + u32 KeyB = KeyBRaw & 0xffffff00; + u32 val = KeyB; + u32 unk = (val < 0); + for (u32 i = 1; i < 24; i++) { + val <<= 1; + unk += (val < 0); + } + if (unk > 14) { + printf("Invalid KeyB - high 24 bits bad: 0x%08x\n",KeyB); + return 0; + } + printf("Valid KeyB: 0x%08x\n",KeyB); + return KeyB; +} + +u32 deriveKeyC(u32 keyCderive, u32 kcrc) { + u32 keyc = 0; + u32 keyCi = 0; + do { + u32 v5 = 0x1000000 * keyCi - 1; + u32 keyCattempt = docrc(kcrc,v5); + //printf("i = %d; keyCderive = %08x; keyCattempt = %08x\n",keyCi,keyCderive,keyCattempt); + if (keyCderive == keyCattempt) { + keyc = v5; + printf("Found keyC: %08x\n",keyc); + return keyc; + } + keyCi++; + } while (keyCi < 256); + return keyc; +} + int main(int argc, char *argv[]) { void *xfb = NULL; @@ -216,19 +199,6 @@ int main(int argc, char *argv[]) PAD_Init(); cmdbuf = memalign(32,32); resbuf = memalign(32,32); - u8 *testdump = memalign(32,0x400000); - if(!testdump) return 0; - if(!fatInitDefault()) - { - printmain(); - fatalError("ERROR: No usable device found to write dumped files to!"); - } - mkdir("/dumps", S_IREAD | S_IWRITE); - if(!dirExists("/dumps")) - { - printmain(); - fatalError("ERROR: Could not create dumps folder, make sure you have a supported device connected!"); - } int i; while(1) { @@ -251,305 +221,98 @@ int main(int argc, char *argv[]) } PAD_ScanPads(); VIDEO_WaitVSync(); - if(PAD_ButtonsHeld(0)) + if(PAD_ButtonsDown(0) & PAD_BUTTON_START) endproc(); } - if(resval & SI_GBA) + if (resval & SI_GBA) { printf("GBA Found! Waiting on BIOS\n"); resbuf[2]=0; - while(!(resbuf[2]&0x10)) + u32 oldresult = 0; + u32 newresult = 0; + // wait for the BIOS to hand over to the game + do { + doreset(); + } while (!(resbuf[1] > 4)); + printf("BIOS handed over to game, waiting on game\n"); + do { doreset(); - getstatus(); - } - printf("Ready, sending dumper\n"); + } while((resbuf[0] != 0) || !(resbuf[2]&0x10)); + // receive the game-code from GBA side. + u32 gamecode = recv(); + printf("Ready, sending multiboot ROM\n"); unsigned int sendsize = ((gba_mb_gba_size+7)&~7); - unsigned int ourkey = calckey(sendsize); + // generate KeyA + unsigned int ourkey = genKeyA(); //printf("Our Key: %08x\n", ourkey); - //get current sessionkey - u32 sessionkeyraw = recv(); - u32 sessionkey = __builtin_bswap32(sessionkeyraw^0x7365646F); - //send over our own key - send(__builtin_bswap32(ourkey)); - unsigned int fcrc = 0x15a0; - //send over gba header - for(i = 0; i < 0xC0; i+=4) - send(__builtin_bswap32(*(vu32*)(gba_mb_gba+i))); - //printf("Header done! Sending ROM...\n"); - for(i = 0xC0; i < sendsize; i+=4) + printf("Sending game code that we got: 0x%08x\n",__builtin_bswap32(gamecode)); + // send the game code back, then KeyA. + send(__builtin_bswap32(gamecode)); + send(ourkey); + // get KeyB from GBA, check it to make sure its valid, then xor with KeyA to derive the initial CRC value and the sessionkey. + u32 sessionkeyraw = 0; + do { + sessionkeyraw = recv(); + } while (sessionkeyraw == gamecode); + sessionkeyraw = checkKeyB(__builtin_bswap32(sessionkeyraw)); + u32 sessionkey = sessionkeyraw ^ ourkey; + u32 kcrc = sessionkey; + printf("start kCRC=%08x\n",kcrc); + sessionkey = (sessionkey*0x6177614b)+1; + // send hacked up send-size in uint32s + u32 hackedupsize = (sendsize >> 3) - 1; + printf("Sending hacked up size 0x%08x\n",hackedupsize); + send(hackedupsize); + //unsigned int fcrc = 0x00bb; + // send over multiboot binary header, in the clear until the end of the nintendo logo. + // GBA checks this, if nintendo logo does not match the one in currently inserted cart's ROM, it will not accept any more data. + for(i = 0; i < 0xA0; i+=4) { + vu32 rom_dword = *(vu32*)(gba_mb_gba+i); + send(__builtin_bswap32(rom_dword)); + } + printf("\n"); + printf("Header done! Sending ROM...\n"); + // Add each uint32 of the multiboot image to the checksum, encrypt the uint32 with the session key, increment the session key, send the encrypted uint32. + for(i = 0xA0; i < sendsize; i+=4) { - u32 enc = ((gba_mb_gba[i+3]<<24)|(gba_mb_gba[i+2]<<16)|(gba_mb_gba[i+1]<<8)|(gba_mb_gba[i])); - fcrc=docrc(fcrc,enc); + u32 dec = ( + ((gba_mb_gba[i+3]) << 24) & 0xff000000 | + ((gba_mb_gba[i+2]) << 16) & 0x00ff0000 | + ((gba_mb_gba[i+1]) << 8) & 0x0000ff00 | + ((gba_mb_gba[i]) << 0) & 0x000000ff + ); + u32 enc = (dec - kcrc) ^ sessionkey; + kcrc=docrc(kcrc,dec); sessionkey = (sessionkey*0x6177614B)+1; - enc^=sessionkey; - enc^=((~(i+(0x20<<20)))+1); - enc^=0x20796220; + //enc^=((~(i+(0x20<<20)))+1); + //enc^=0x6f646573;//0x20796220; send(enc); } - fcrc |= (sendsize<<16); - //printf("ROM done! CRC: %08x\n", fcrc); - //send over CRC - sessionkey = (sessionkey*0x6177614B)+1; - fcrc^=sessionkey; - fcrc^=((~(i+(0x20<<20)))+1); - fcrc^=0x20796220; - send(fcrc); + //fcrc |= (sendsize<<16); + printf("ROM done! CRC: %08x\n", kcrc); //get crc back (unused) - recv(); - printf("Done!\n"); - sleep(2); - //hm - while(1) - { - printmain(); - printf("Press A once you have a GBA Game inserted.\n"); - printf("Press Y to backup the GBA BIOS.\n \n"); - PAD_ScanPads(); - VIDEO_WaitVSync(); - u32 btns = PAD_ButtonsDown(0); - if(btns&PAD_BUTTON_START) - endproc(); - else if(btns&PAD_BUTTON_A) - { - if(recv() == 0) //ready - { - printf("Waiting for GBA\n"); - VIDEO_WaitVSync(); - int gbasize = 0; - while(gbasize == 0) - gbasize = __builtin_bswap32(recv()); - send(0); //got gbasize - u32 savesize = __builtin_bswap32(recv()); - send(0); //got savesize - if(gbasize == -1) - { - warnError("ERROR: No (Valid) GBA Card inserted!\n"); - continue; - } - //get rom header - for(i = 0; i < 0xC0; i+=4) - *(vu32*)(testdump+i) = recv(); - //print out all the info from the game - printf("Game Name: %.12s\n",(char*)(testdump+0xA0)); - printf("Game ID: %.4s\n",(char*)(testdump+0xAC)); - printf("Company ID: %.2s\n",(char*)(testdump+0xB0)); - printf("ROM Size: %02.02f MB\n",((float)(gbasize/1024))/1024.f); - if(savesize > 0) - printf("Save Size: %02.02f KB\n \n",((float)(savesize))/1024.f); - else - printf("No Save File\n \n"); - //generate file paths - char gamename[64]; - sprintf(gamename,"/dumps/%.12s [%.4s%.2s].gba", - (char*)(testdump+0xA0),(char*)(testdump+0xAC),(char*)(testdump+0xB0)); - fixFName(gamename+7); //fix name behind "/dumps/" - char savename[64]; - sprintf(savename,"/dumps/%.12s [%.4s%.2s].sav", - (char*)(testdump+0xA0),(char*)(testdump+0xAC),(char*)(testdump+0xB0)); - fixFName(savename+7); //fix name behind "/dumps/" - //let the user choose the option - printf("Press A to dump this game, it will take about %i minutes.\n",gbasize/1024/1024*3/2); - printf("Press B if you want to cancel dumping this game.\n"); - if(savesize > 0) - { - printf("Press Y to backup this save file.\n"); - printf("Press X to restore this save file.\n"); - printf("Press Z to clear the save file on the GBA Cartridge.\n\n"); - } - else - printf("\n"); - int command = 0; - while(1) - { - PAD_ScanPads(); - VIDEO_WaitVSync(); - u32 btns = PAD_ButtonsDown(0); - if(btns&PAD_BUTTON_START) - endproc(); - else if(btns&PAD_BUTTON_A) - { - command = 1; - break; - } - else if(btns&PAD_BUTTON_B) - break; - else if(savesize > 0) - { - if(btns&PAD_BUTTON_Y) - { - command = 2; - break; - } - else if(btns&PAD_BUTTON_X) - { - command = 3; - break; - } - else if(btns&PAD_TRIGGER_Z) - { - command = 4; - break; - } - } - } - if(command == 1) - { - FILE *f = fopen(gamename,"rb"); - if(f) - { - fclose(f); - command = 0; - warnError("ERROR: Game already dumped!\n"); - } - } - else if(command == 2) - { - FILE *f = fopen(savename,"rb"); - if(f) - { - fclose(f); - command = 0; - warnError("ERROR: Save already backed up!\n"); - } - } - else if(command == 3) - { - size_t readsize = 0; - FILE *f = fopen(savename,"rb"); - if(f) - { - fseek(f,0,SEEK_END); - readsize = ftell(f); - if(readsize != savesize) - { - command = 0; - warnError("ERROR: Save has the wrong size, aborting restore!\n"); - } - else - { - rewind(f); - fread(testdump,readsize,1,f); - } - fclose(f); - } - else - { - command = 0; - warnError("ERROR: No Save to restore!\n"); - } - } - send(command); - //let gba prepare - sleep(1); - if(command == 0) - continue; - else if(command == 1) - { - //create base file with size - printf("Preparing file...\n"); - createFile(gamename,gbasize); - FILE *f = fopen(gamename,"wb"); - if(!f) - fatalError("ERROR: Could not create file! Exit..."); - printf("Dumping...\n"); - u32 bytes_read = 0; - while(gbasize > 0) - { - int toread = (gbasize > 0x400000 ? 0x400000 : gbasize); - int j; - for(j = 0; j < toread; j+=4) - { - *(vu32*)(testdump+j) = recv(); - bytes_read+=4; - if((bytes_read&0xFFFF) == 0) - printf("\r%02.02f MB done",(float)(bytes_read/1024)/1024.f); - } - fwrite(testdump,toread,1,f); - gbasize -= toread; - } - printf("\nClosing file\n"); - fclose(f); - printf("Game dumped!\n"); - sleep(5); - } - else if(command == 2) - { - //create base file with size - printf("Preparing file...\n"); - createFile(savename,savesize); - FILE *f = fopen(savename,"wb"); - if(!f) - fatalError("ERROR: Could not create file! Exit..."); - printf("Waiting for GBA\n"); - VIDEO_WaitVSync(); - u32 readval = 0; - while(readval != savesize) - readval = __builtin_bswap32(recv()); - send(0); //got savesize - printf("Receiving...\n"); - for(i = 0; i < savesize; i+=4) - *(vu32*)(testdump+i) = recv(); - printf("Writing save...\n"); - fwrite(testdump,savesize,1,f); - fclose(f); - printf("Save backed up!\n"); - sleep(5); - } - else if(command == 3 || command == 4) - { - u32 readval = 0; - while(readval != savesize) - readval = __builtin_bswap32(recv()); - if(command == 3) - { - printf("Sending save\n"); - VIDEO_WaitVSync(); - for(i = 0; i < savesize; i+=4) - send(__builtin_bswap32(*(vu32*)(testdump+i))); - } - printf("Waiting for GBA\n"); - while(recv() != 0) - VIDEO_WaitVSync(); - printf(command == 3 ? "Save restored!\n" : "Save cleared!\n"); - send(0); - sleep(5); - } - } - } - else if(btns&PAD_BUTTON_Y) - { - const char *biosname = "/dumps/gba_bios.bin"; - FILE *f = fopen(biosname,"rb"); - if(f) - { - fclose(f); - warnError("ERROR: BIOS already backed up!\n"); - } - else - { - //create base file with size - printf("Preparing file...\n"); - createFile(biosname,0x4000); - f = fopen(biosname,"wb"); - if(!f) - fatalError("ERROR: Could not create file! Exit..."); - //send over bios dump command - send(5); - //the gba might still be in a loop itself - sleep(1); - //lets go! - printf("Dumping...\n"); - for(i = 0; i < 0x4000; i+=4) - *(vu32*)(testdump+i) = recv(); - fwrite(testdump,0x4000,1,f); - printf("Closing file\n"); - fclose(f); - printf("BIOS dumped!\n"); - sleep(5); - } - } - } + // Get KeyC derivation material from GBA (eventually) + u32 keyCderive = 0; + do { + keyCderive = recv(); + } while (keyCderive <= 0xfeffffff); + keyCderive = __builtin_bswap32(keyCderive); + keyCderive >>= 8; + printf("KeyC derivation material: %08x\n",keyCderive); + + // (try to) find the KeyC, using the checksum of the multiboot image, and the derivation material that GBA sent to us + + u32 keyc = deriveKeyC(keyCderive,kcrc); + if (keyc == 0) printf("Could not find keyC - kcrc=0x%08x\n",kcrc); + + // derive the boot key from the found KeyC, and send to GBA. if this is not correct, GBA will not jump to the multiboot image it was sent. + u32 bootkey = docrc(0xBB,keyc) | 0xbb000000; + printf("BootKey = 0x%08x\n",bootkey); + send(bootkey); + + printf("Done! Press any key to start sending again.\n"); + do { PAD_ScanPads(); } while (!PAD_ButtonsDown(0)); } } return 0; -- cgit 1.4.1 From 272a6592bace32cc4b2c5432aa3fad4066f152c6 Mon Sep 17 00:00:00 2001 From: slipstream/RoL Date: Sun, 19 Feb 2017 18:56:09 +0000 Subject: make sure data directory exists Fixes build errors --- build.bat | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.bat b/build.bat index 8a35180..17c2889 100644 --- a/build.bat +++ b/build.bat @@ -3,9 +3,10 @@ cd gba make clean make cd .. +@md data mv -f gba/gba_pkjb.gba data/gba_mb.gba make -f Makefile.gc clean make -f Makefile.gc make -f Makefile.wii clean make -f Makefile.wii -pause \ No newline at end of file +pause -- cgit 1.4.1 From d448823ffb540cb462548552efe4b5650e66de95 Mon Sep 17 00:00:00 2001 From: slipstream/RoL Date: Mon, 20 Feb 2017 18:29:22 +0000 Subject: Add support for almost all Gen III games MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The only Gen III game unsupported should be Pokémon LeafGreen v1.1 (Japan); as it is undumped. A couple of bugs have also been fixed. The code has also been refactored a little; now payload code goes into `payload.c`. The codebase is also ready for a planned future change to include savedata structure definitions. This will be done when it's done -- PRs to help would be appreciated! --- gba/Makefile | 2 +- gba/source/call_into_middle_of_titlescreen_func.s | 13 ++ gba/source/main.c | 250 ++++++++++++++++++++-- gba/source/payload.c | 18 ++ gba/source/payload.h | 12 ++ gba/source/saveblocks.h | 15 ++ gba/start/pkjb_crt0.s | 4 + 7 files changed, 299 insertions(+), 15 deletions(-) create mode 100644 gba/source/call_into_middle_of_titlescreen_func.s create mode 100644 gba/source/payload.c create mode 100644 gba/source/payload.h create mode 100644 gba/source/saveblocks.h diff --git a/gba/Makefile b/gba/Makefile index f9cb296..b28e0e1 100644 --- a/gba/Makefile +++ b/gba/Makefile @@ -32,7 +32,7 @@ INCLUDES := #--------------------------------------------------------------------------------- ARCH := -mthumb -mthumb-interwork -CFLAGS := -g -Wall -O3\ +CFLAGS := -g -Wall -Wno-multichar -O3\ -mcpu=arm7tdmi -mtune=arm7tdmi\ -fomit-frame-pointer\ -ffast-math \ diff --git a/gba/source/call_into_middle_of_titlescreen_func.s b/gba/source/call_into_middle_of_titlescreen_func.s new file mode 100644 index 0000000..7908f6b --- /dev/null +++ b/gba/source/call_into_middle_of_titlescreen_func.s @@ -0,0 +1,13 @@ + .text + .code 16 + + .global call_into_middle_of_titlescreen_func + .thumb_func +call_into_middle_of_titlescreen_func: + push {lr} + push {r4-r5} + @ use r4 (which already got saved to the stack) as scratch space to reserve a variable amount of stack space + mov r4,sp + sub r4,r4,r1 + mov sp,r4 + bx r0 \ No newline at end of file diff --git a/gba/source/main.c b/gba/source/main.c index ce6969b..5e2b708 100644 --- a/gba/source/main.c +++ b/gba/source/main.c @@ -1,42 +1,264 @@ /* * Example Gen3-multiboot payload by slipstream/RoL 2017. - * Supports only English Ruby, v1.0-1.2. * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. + * + * main.c: setup, call payload, return gracefully back to game */ #include +#include "payload.h" + +void call_into_middle_of_titlescreen_func(u32 addr,u32 stackspace); int main(void) { // check the ROM code, make sure this game is supported. - char* ROM = 0x8000000; + u8* ROM = (u8*) 0x8000000; - if ((*(u32*)(&ROM[0xAC])) != 'EVXA') return 0; // Pokémon Ruby english, nothing else supported! + u32 gamecode = (*(u32*)(&ROM[0xAC])); void(*loadsave)(char a1); + void(*mainloop)(); + pSaveBlock1 gSaveBlock1; + pSaveBlock2 gSaveBlock2; + pSaveBlock3 gSaveBlock3; + u32 titlemid = 0; // get the address of the save loading function. - switch (ROM[0xBC]) { // version number - case 0: - loadsave = 0x8125EC9; + switch (gamecode) { + // --- R/S --- + case 'DVXA': // Ruby German + case 'DPXA': // Sapphire German + // TODO: detect debug ROM? + gSaveBlock1 = (pSaveBlock1) 0x2025734; + gSaveBlock2 = (pSaveBlock2) 0x2024EA4; + gSaveBlock3 = (pSaveBlock3) 0x20300A0; + loadsave = (void(*)(char)) 0x8126249; // same for v1.0 + v1.1 + mainloop = (void(*)()) 0x80003D9; + break; + case 'FVXA': // Ruby French + case 'FPXA': // Sapphire French + gSaveBlock1 = (pSaveBlock1) 0x2025734; + gSaveBlock2 = (pSaveBlock2) 0x2024EA4; + gSaveBlock3 = (pSaveBlock3) 0x20300A0; + loadsave = (void(*)(char)) 0x8126351; // same for v1.0 + v1.1 + mainloop = (void(*)()) 0x80003D9; + break; + case 'IVXA': // Ruby Italian + case 'IPXA': // Sapphire Italian + gSaveBlock1 = (pSaveBlock1) 0x2025734; + gSaveBlock2 = (pSaveBlock2) 0x2024EA4; + gSaveBlock3 = (pSaveBlock3) 0x20300A0; + loadsave = (void(*)(char)) 0x8126249; // same for v1.0 + v1.1 + mainloop = (void(*)()) 0x80003D9; + break; + case 'SVXA': // Ruby Spanish + case 'SPXA': // Sapphire Spanish + gSaveBlock1 = (pSaveBlock1) 0x2025734; + gSaveBlock2 = (pSaveBlock2) 0x2024EA4; + gSaveBlock3 = (pSaveBlock3) 0x20300A0; + loadsave = (void(*)(char)) 0x8126349; // same for v1.0 + v1.1 + mainloop = (void(*)()) 0x80003D9; + break; + case 'EVXA': // Ruby English + case 'EPXA': // Sapphire English + gSaveBlock1 = (pSaveBlock1) 0x2025734; + gSaveBlock2 = (pSaveBlock2) 0x2024EA4; + gSaveBlock3 = (pSaveBlock3) 0x20300A0; + mainloop = (void(*)()) 0x80002A5; + switch (ROM[0xBC]) { // version number + case 0: + loadsave = (void(*)(char)) 0x8125EC9; + break; + case 1: + case 2: + loadsave = (void(*)(char)) 0x8125EE9; + break; + default: + return 0; // unsupported version + } + break; + case 'JVXA': // Ruby Japanese + case 'JPXA': // Sapphire Japanese + gSaveBlock1 = (pSaveBlock1) 0x2025494; + gSaveBlock2 = (pSaveBlock2) 0x2024C04; + gSaveBlock3 = (pSaveBlock3) 0x202FDBC; + loadsave = (void(*)(char)) 0x8120d05; // same for v1.0 + v1.1 + mainloop = (void(*)()) 0x80002A9; + break; + /// --- FR/LG --- + // In FR/LG, the function that initialises the save-block pointers to default does not set up saveblock3. + // Which will need to be set up before loading the save if we want boxed Pokémon to not disappear. + // Oh, and loadsave() offset is different between FR and LG... + case 'DRPB': // FireRed German + case 'DGPB': // LeafGreen German + gSaveBlock1 = (pSaveBlock1) 0x202552C; + gSaveBlock2 = (pSaveBlock2) 0x2024588; + gSaveBlock3 = (pSaveBlock3) 0x2029314; + *(pSaveBlock3*)(0x3004f60) = gSaveBlock3; + loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80da721 : 0x80da6f5 ); + mainloop = (void(*)()) 0x8000425; + titlemid = 0x80791df; + break; + case 'FRPB': // FireRed French + case 'FGPB': // LeafGreen French + gSaveBlock1 = (pSaveBlock1) 0x202552C; + gSaveBlock2 = (pSaveBlock2) 0x2024588; + gSaveBlock3 = (pSaveBlock3) 0x2029314; + *(pSaveBlock3*)(0x3004f60) = gSaveBlock3; + loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80da7e1 : 0x80da7b5 ); + mainloop = (void(*)()) 0x8000417; + titlemid = 0x807929f; + break; + case 'IRPB': // FireRed Italian + case 'IGPB': // LeafGreen Italian + gSaveBlock1 = (pSaveBlock1) 0x202552C; + gSaveBlock2 = (pSaveBlock2) 0x2024588; + gSaveBlock3 = (pSaveBlock3) 0x2029314; + *(pSaveBlock3*)(0x3004f60) = gSaveBlock3; + loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80da721 : 0x80da6f5 ); + mainloop = (void(*)()) 0x8000425; + titlemid = 0x80791cb; break; - case 1: - case 2: - loadsave = 0x8125EE9; + case 'SRPB': // FireRed Spanish + case 'SGPB': // LeafGreen Spanish + gSaveBlock1 = (pSaveBlock1) 0x202552C; + gSaveBlock2 = (pSaveBlock2) 0x2024588; + gSaveBlock3 = (pSaveBlock3) 0x2029314; + *(pSaveBlock3*)(0x3004f60) = gSaveBlock3; + loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80da809 : 0x80da7dd ); + mainloop = (void(*)()) 0x8000417; + titlemid = 0x80792b3; + break; + case 'ERPB': // FireRed English + case 'EGPB': // LeafGreen English + gSaveBlock1 = (pSaveBlock1) 0x202552C; + gSaveBlock2 = (pSaveBlock2) 0x2024588; + gSaveBlock3 = (pSaveBlock3) 0x2029314; + *(pSaveBlock3*)(0x3005010) = gSaveBlock3; + switch (ROM[0xBC]) { // version number + case 0: + loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80da4fd : 0x80da4d1 ); + mainloop = (void(*)()) 0x800041b; + titlemid = 0x807927b; + break; + case 1: + loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80da511 : 0x80da4e5 ); + mainloop = (void(*)()) 0x8000429; + titlemid = 0x807928f; + break; + default: + return 0; // unsupported version + } + break; + case 'JRPB': // FireRed Japanese + case 'JGPB': // LeafGreen Japanese + gSaveBlock1 = (pSaveBlock1) 0x202548C; + gSaveBlock2 = (pSaveBlock2) 0x20244E8; + gSaveBlock3 = (pSaveBlock3) 0x202924C; + *(pSaveBlock3*)(0x3005050) = gSaveBlock3; + switch (ROM[0xBC]) { // version number + case 0: + loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80db4e5 : 0x80db4b9 ); + mainloop = (void(*)()) 0x800041b; + titlemid = ( (gamecode << 8) == 'RPB\x00' ? 0x8078a0d : 0x8078a0f ); + break; + case 1: + if ((gamecode << 8) == 'GPB\x00') { + // LeafGreen v1.1 Japanese is undumped. + // Therefore, it is unsupported. + // I will make guesses at the offsets in the comments, but I will not actually implement them until LeafGreen v1.1 is dumped. + return 0; + } + loadsave = (void(*)(char)) 0x80db529; // potential LG1.1 address: 0x80db4fd + mainloop = (void(*)()) 0x8000417; + titlemid = 0x8078987; // potential LG1.1 address: 0x8078989 + break; + default: + return 0; // unsupported version + } + break; + /// --- Emerald --- + // In Emerald, the saveblock pointer that isn't set up is saveblock1 (in FR/LG it was saveblock3). + // The initial save loading code after the copyright screen is also updated, now it sets up ASLR/crypto here before loading the save. + case 'DEPB': // Emerald German + gSaveBlock1 = (pSaveBlock1) 0x2025A00; + gSaveBlock2 = (pSaveBlock2) 0x2024A54; + gSaveBlock3 = (pSaveBlock3) 0x2029808; + *(pSaveBlock1*)(0x3005d8c) = gSaveBlock1; + loadsave = (void(*)(char)) 0x8153075; + mainloop = (void(*)()) 0x800042b; + titlemid = 0x816fdb5; + break; + case 'FEPB': // Emerald French + gSaveBlock1 = (pSaveBlock1) 0x2025A00; + gSaveBlock2 = (pSaveBlock2) 0x2024A54; + gSaveBlock3 = (pSaveBlock3) 0x2029808; + *(pSaveBlock1*)(0x3005d8c) = gSaveBlock1; + loadsave = (void(*)(char)) 0x815319d; + mainloop = (void(*)()) 0x800042b; + titlemid = 0x816fedd; + break; + case 'IEPB': // Emerald Italian + gSaveBlock1 = (pSaveBlock1) 0x2025A00; + gSaveBlock2 = (pSaveBlock2) 0x2024A54; + gSaveBlock3 = (pSaveBlock3) 0x2029808; + *(pSaveBlock1*)(0x3005d8c) = gSaveBlock1; + loadsave = (void(*)(char)) 0x8153065; + mainloop = (void(*)()) 0x800042b; + titlemid = 0x816fda5; + break; + case 'SEPB': // Emerald Spanish + gSaveBlock1 = (pSaveBlock1) 0x2025A00; + gSaveBlock2 = (pSaveBlock2) 0x2024A54; + gSaveBlock3 = (pSaveBlock3) 0x2029808; + *(pSaveBlock1*)(0x3005d8c) = gSaveBlock1; + loadsave = (void(*)(char)) 0x8153175; + mainloop = (void(*)()) 0x800042b; + titlemid = 0x816feb5; + break; + case 'EEPB': // Emerald English + gSaveBlock1 = (pSaveBlock1) 0x2025A00; + gSaveBlock2 = (pSaveBlock2) 0x2024A54; + gSaveBlock3 = (pSaveBlock3) 0x2029808; + *(pSaveBlock1*)(0x3005d8c) = gSaveBlock1; + loadsave = (void(*)(char)) 0x81534d1; + mainloop = (void(*)()) 0x800042b; + titlemid = 0x817014d; + break; + case 'JEPB': // Emerald Japanese + gSaveBlock1 = (pSaveBlock1) 0x20256A4; + gSaveBlock2 = (pSaveBlock2) 0x20246F8; + gSaveBlock3 = (pSaveBlock3) 0x20294AC; + *(pSaveBlock1*)(0x3005aec) = gSaveBlock1; + loadsave = (void(*)(char)) 0x815340d; + mainloop = (void(*)()) 0x800042b; + titlemid = 0x816ff45; break; default: - return 0; //bail out + return 0; // this game isn't supported } loadsave(0); // now the save is loaded, we can do what we want with the loaded blocks. - // here as a small PoC, changing first letter of player name to 'z'. - u8* gSaveBlock2 = 0x2024EA4; - gSaveBlock2[0] = 0xee; // 'z' + // time to call the payload. + payload(gSaveBlock1,gSaveBlock2,gSaveBlock3); + // In FR/LG/Emerald, just returning to the game is unwise. + // The game reloads the savefile. + // In FR/LG, this is done at the title screen after setting ASLR/saveblock-crypto up. (probably because at initial save-load, SaveBlock3 ptr isn't set up lol) + // So, better bypass the title screen and get the game to return directly to the Continue/New Game screen. + // In Emerald, the save reload happens after the Continue option was chosen, so we have no choice but to bypass everything and get the game to go straight to the overworld. + // Easiest way to do this is to call into the middle of the function we want, using an ASM wrapper to set up the stack. + // Here goes... + if (titlemid) { + // Function reserves an extra 4 bytes of stack space in FireRed, and none in Emerald. + call_into_middle_of_titlescreen_func(titlemid,((gamecode << 8) == 'EPB\x00' ? 0 : 4)); + } // Now we've done what we want, time to return to the game. // Can't just return, the game will reload the save. // So let's just call the main-loop directly ;) - void(*mainloop)() = 0x80002A5; // turn the sound back on before we head back to the game *(vu16 *)(REG_BASE + 0x84) = 0x8f; + // re-enable interrupts + REG_IME = 1; mainloop(); // Anything past here will not be executed. return 0; diff --git a/gba/source/payload.c b/gba/source/payload.c new file mode 100644 index 0000000..7015774 --- /dev/null +++ b/gba/source/payload.c @@ -0,0 +1,18 @@ +/* + * Example Gen3-multiboot payload by slipstream/RoL 2017. + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + * + * payload.c: place where user payload should go :) + */ + +#include +#include "payload.h" + +// Your payload code should obviously go into the body of this, the payload function. +void payload(pSaveBlock1 SaveBlock1,pSaveBlock2 SaveBlock2,pSaveBlock3 SaveBlock3) { + // This example payload will modify the first character of the player's name. + // It will change to 'z'. You can see the character encoding table here: http://bulbapedia.bulbagarden.net/wiki/Character_encoding_in_Generation_III + SaveBlock2[0] = 0xee; // 'z' +} \ No newline at end of file diff --git a/gba/source/payload.h b/gba/source/payload.h new file mode 100644 index 0000000..3f9ca62 --- /dev/null +++ b/gba/source/payload.h @@ -0,0 +1,12 @@ +/* + * Example Gen3-multiboot payload by slipstream/RoL 2017. + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + * + * payload.h: header file describing payload function + */ + +#include "saveblocks.h" + +void payload(pSaveBlock1 SaveBlock1,pSaveBlock2 SaveBlock2,pSaveBlock3 SaveBlock3); \ No newline at end of file diff --git a/gba/source/saveblocks.h b/gba/source/saveblocks.h new file mode 100644 index 0000000..45c127c --- /dev/null +++ b/gba/source/saveblocks.h @@ -0,0 +1,15 @@ +/* + * Example Gen3-multiboot payload by slipstream/RoL 2017. + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + * + * saveblocks.h: describes saveblock structures for all of Gen 3 (yay!) + */ + +// TODO: this entire file. Placeholders for now, fill in later, if I can be bothered. +// I don't really want to make a fork of pokeruby's headers for now... + +typedef u8 SaveBlock1, *pSaveBlock1; +typedef u8 SaveBlock2, *pSaveBlock2; +typedef u8 SaveBlock3, *pSaveBlock3; \ No newline at end of file diff --git a/gba/start/pkjb_crt0.s b/gba/start/pkjb_crt0.s index 1bf5bd7..0d9d657 100644 --- a/gba/start/pkjb_crt0.s +++ b/gba/start/pkjb_crt0.s @@ -85,6 +85,10 @@ start_vector: ldr r1, =0x4000084 mov r0, #0x8F strh r0, [r1] +@; Also turn interrupts back on + ldr r1, =0x4000208 + mov r0, #1 + str r0, [r1] pop {pc} @--------------------------------------------------------------------------------- -- cgit 1.4.1 From 3600f4249f39ec37e41e57aed70c8af620ff847c Mon Sep 17 00:00:00 2001 From: slipstream/RoL Date: Tue, 21 Feb 2017 20:50:41 +0000 Subject: New features and bugfixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixed FireRed v1.0 (Japan) support - Now reloads the Pokémon from the loaded savefile after calling the payload, so the payload can modify those parts of saveBlock1 directly - Decrypts "secure" save data areas in FireRed, LeafGreen and Emerald --- gba/source/main.c | 135 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 124 insertions(+), 11 deletions(-) diff --git a/gba/source/main.c b/gba/source/main.c index 5e2b708..4e1e31f 100644 --- a/gba/source/main.c +++ b/gba/source/main.c @@ -11,6 +11,92 @@ void call_into_middle_of_titlescreen_func(u32 addr,u32 stackspace); +void decrypt_save_structures(pSaveBlock1 SaveBlock1,pSaveBlock2 SaveBlock2,pSaveBlock3 SaveBlock3) { + if (GAME_RS) { + // R/S doesn't have save crypto. + return; + } + u8* sb1raw = (u8*)SaveBlock1; + u8* sb2raw = (u8*)SaveBlock2; + //u8* sb3raw = (u8*)SaveBlock3; // unused + + u32* xor_key_ptr = (u32*)(&sb2raw[( GAME_EM ? 0xA8 : 0xF20 )]); + + u32 xor_key = *xor_key_ptr; + u16 xor_key16 = (u16)xor_key; + if (!xor_key) { + // xor key is zero, nothing needs to be done. + return; + } + + u32* ptr_to_xor; + u32 save_offset; + int i; + u32* bag_pocket_offsets; + u32* bag_pocket_counts; + if (GAME_FRLG) { + // loop over and decrypt various things + save_offset = 0x3D38 + 4; + for (i = 3; i >= 0; i--) { + ptr_to_xor = (u32*)(&sb1raw[save_offset]); + *ptr_to_xor ^= xor_key; + save_offset += 12; + } + for (i = 0; i <= 0x3f; i++) { + save_offset = 0x1200 + (i*sizeof(u32)); + ptr_to_xor = (u32*)(&sb1raw[save_offset]); + *ptr_to_xor ^= xor_key; + } + // loop over each of the bag pockets and decrypt decrypt decrypt + bag_pocket_offsets = (u32[5]) { 0x310, 0x388, 0x430, 0x464, 0x54C }; + bag_pocket_counts = (u32[5]) { 42, 30, 13, 58, 43 }; + for (i = 0; i < 5; i++) { + for (int bag_i = 0; bag_i < bag_pocket_counts[i]; bag_i++) { + save_offset = bag_pocket_offsets[i] + (sizeof(u32) * bag_i) + 2; + *(u16*)(&sb1raw[save_offset]) ^= xor_key16; + } + } + // decrypt some more stuff + save_offset = 0xaf8; + ptr_to_xor = (u32*)(&sb1raw[save_offset]); + *ptr_to_xor ^= xor_key; + save_offset = 0x290; + ptr_to_xor = (u32*)(&sb1raw[save_offset]); + *ptr_to_xor ^= xor_key; + save_offset = 0x294; + *(u16*)(&sb1raw[save_offset]) ^= xor_key16; + } else { // Emerald + // loop over and decrypt various things + for (i = 0; i <= 0x3f; i++) { + save_offset = 0x159c + (i*sizeof(u32)); + ptr_to_xor = (u32*)(&sb1raw[save_offset]); + *ptr_to_xor ^= xor_key; + } + // loop over each of the bag pockets and decrypt decrypt decrypt + bag_pocket_offsets = (u32[5]) { 0x560, 0x5D8, 0x650, 0x690, 0x790 }; + bag_pocket_counts = (u32[5]) { 30, 30, 16, 64, 46 }; + for (i = 0; i < 5; i++) { + for (int bag_i = 0; bag_i < bag_pocket_counts[i]; bag_i++) { + save_offset = bag_pocket_offsets[i] + (sizeof(u32) * bag_i) + 2; + *(u16*)(&sb1raw[save_offset]) ^= xor_key16; + } + } + // decrypt some more stuff + save_offset = 0x1F4; + ptr_to_xor = (u32*)(&sb1raw[save_offset]); + *ptr_to_xor ^= xor_key; + save_offset = 0x490; + ptr_to_xor = (u32*)(&sb1raw[save_offset]); + *ptr_to_xor ^= xor_key; + save_offset = 0x494; + *(u16*)(&sb1raw[save_offset]) ^= xor_key16; + } + + *xor_key_ptr = 0; + return; + +} + int main(void) { // check the ROM code, make sure this game is supported. u8* ROM = (u8*) 0x8000000; @@ -19,6 +105,7 @@ int main(void) { void(*loadsave)(char a1); void(*mainloop)(); + void(*load_pokemon)(); pSaveBlock1 gSaveBlock1; pSaveBlock2 gSaveBlock2; pSaveBlock3 gSaveBlock3; @@ -34,6 +121,7 @@ int main(void) { gSaveBlock3 = (pSaveBlock3) 0x20300A0; loadsave = (void(*)(char)) 0x8126249; // same for v1.0 + v1.1 mainloop = (void(*)()) 0x80003D9; + load_pokemon = (void(*)()) 0x8047da9; break; case 'FVXA': // Ruby French case 'FPXA': // Sapphire French @@ -42,6 +130,7 @@ int main(void) { gSaveBlock3 = (pSaveBlock3) 0x20300A0; loadsave = (void(*)(char)) 0x8126351; // same for v1.0 + v1.1 mainloop = (void(*)()) 0x80003D9; + load_pokemon = (void(*)()) 0x8047e95; break; case 'IVXA': // Ruby Italian case 'IPXA': // Sapphire Italian @@ -50,6 +139,7 @@ int main(void) { gSaveBlock3 = (pSaveBlock3) 0x20300A0; loadsave = (void(*)(char)) 0x8126249; // same for v1.0 + v1.1 mainloop = (void(*)()) 0x80003D9; + load_pokemon = (void(*)()) 0x8047dbd; break; case 'SVXA': // Ruby Spanish case 'SPXA': // Sapphire Spanish @@ -58,6 +148,7 @@ int main(void) { gSaveBlock3 = (pSaveBlock3) 0x20300A0; loadsave = (void(*)(char)) 0x8126349; // same for v1.0 + v1.1 mainloop = (void(*)()) 0x80003D9; + load_pokemon = (void(*)()) 0x8047ea5; break; case 'EVXA': // Ruby English case 'EPXA': // Sapphire English @@ -68,10 +159,12 @@ int main(void) { switch (ROM[0xBC]) { // version number case 0: loadsave = (void(*)(char)) 0x8125EC9; + load_pokemon = (void(*)()) 0x8047a85; break; case 1: case 2: loadsave = (void(*)(char)) 0x8125EE9; + load_pokemon = (void(*)()) 0x8047aa5; break; default: return 0; // unsupported version @@ -84,6 +177,7 @@ int main(void) { gSaveBlock3 = (pSaveBlock3) 0x202FDBC; loadsave = (void(*)(char)) 0x8120d05; // same for v1.0 + v1.1 mainloop = (void(*)()) 0x80002A9; + load_pokemon = (void(*)()) 0x8044d55; break; /// --- FR/LG --- // In FR/LG, the function that initialises the save-block pointers to default does not set up saveblock3. @@ -95,9 +189,10 @@ int main(void) { gSaveBlock2 = (pSaveBlock2) 0x2024588; gSaveBlock3 = (pSaveBlock3) 0x2029314; *(pSaveBlock3*)(0x3004f60) = gSaveBlock3; - loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80da721 : 0x80da6f5 ); + loadsave = (void(*)(char)) ( GAME_FR ? 0x80da721 : 0x80da6f5 ); mainloop = (void(*)()) 0x8000425; titlemid = 0x80791df; + load_pokemon = (void(*)()) 0x804c251; break; case 'FRPB': // FireRed French case 'FGPB': // LeafGreen French @@ -105,9 +200,10 @@ int main(void) { gSaveBlock2 = (pSaveBlock2) 0x2024588; gSaveBlock3 = (pSaveBlock3) 0x2029314; *(pSaveBlock3*)(0x3004f60) = gSaveBlock3; - loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80da7e1 : 0x80da7b5 ); + loadsave = (void(*)(char)) ( GAME_FR ? 0x80da7e1 : 0x80da7b5 ); mainloop = (void(*)()) 0x8000417; titlemid = 0x807929f; + load_pokemon = (void(*)()) 0x804c311; break; case 'IRPB': // FireRed Italian case 'IGPB': // LeafGreen Italian @@ -115,9 +211,10 @@ int main(void) { gSaveBlock2 = (pSaveBlock2) 0x2024588; gSaveBlock3 = (pSaveBlock3) 0x2029314; *(pSaveBlock3*)(0x3004f60) = gSaveBlock3; - loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80da721 : 0x80da6f5 ); + loadsave = (void(*)(char)) ( GAME_FR ? 0x80da721 : 0x80da6f5 ); mainloop = (void(*)()) 0x8000425; titlemid = 0x80791cb; + load_pokemon = (void(*)()) 0x804c23d; break; case 'SRPB': // FireRed Spanish case 'SGPB': // LeafGreen Spanish @@ -125,9 +222,10 @@ int main(void) { gSaveBlock2 = (pSaveBlock2) 0x2024588; gSaveBlock3 = (pSaveBlock3) 0x2029314; *(pSaveBlock3*)(0x3004f60) = gSaveBlock3; - loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80da809 : 0x80da7dd ); + loadsave = (void(*)(char)) ( GAME_FR ? 0x80da809 : 0x80da7dd ); mainloop = (void(*)()) 0x8000417; titlemid = 0x80792b3; + load_pokemon = (void(*)()) 0x804c325; break; case 'ERPB': // FireRed English case 'EGPB': // LeafGreen English @@ -137,14 +235,16 @@ int main(void) { *(pSaveBlock3*)(0x3005010) = gSaveBlock3; switch (ROM[0xBC]) { // version number case 0: - loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80da4fd : 0x80da4d1 ); + loadsave = (void(*)(char)) ( GAME_FR ? 0x80da4fd : 0x80da4d1 ); mainloop = (void(*)()) 0x800041b; titlemid = 0x807927b; + load_pokemon = (void(*)()) 0x804c231; break; case 1: - loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80da511 : 0x80da4e5 ); + loadsave = (void(*)(char)) ( GAME_FR ? 0x80da511 : 0x80da4e5 ); mainloop = (void(*)()) 0x8000429; titlemid = 0x807928f; + load_pokemon = (void(*)()) 0x804c245; break; default: return 0; // unsupported version @@ -158,9 +258,10 @@ int main(void) { *(pSaveBlock3*)(0x3005050) = gSaveBlock3; switch (ROM[0xBC]) { // version number case 0: - loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80db4e5 : 0x80db4b9 ); + loadsave = (void(*)(char)) ( GAME_FR ? 0x80db4e5 : 0x80db4b9 ); mainloop = (void(*)()) 0x800041b; - titlemid = ( (gamecode << 8) == 'RPB\x00' ? 0x8078a0d : 0x8078a0f ); + titlemid = 0x8078a0f; + load_pokemon = (void(*)()) 0x804b9e9; break; case 1: if ((gamecode << 8) == 'GPB\x00') { @@ -171,7 +272,8 @@ int main(void) { } loadsave = (void(*)(char)) 0x80db529; // potential LG1.1 address: 0x80db4fd mainloop = (void(*)()) 0x8000417; - titlemid = 0x8078987; // potential LG1.1 address: 0x8078989 + titlemid = 0x8078987; + load_pokemon = (void(*)()) 0x804b9c5; break; default: return 0; // unsupported version @@ -188,6 +290,7 @@ int main(void) { loadsave = (void(*)(char)) 0x8153075; mainloop = (void(*)()) 0x800042b; titlemid = 0x816fdb5; + load_pokemon = (void(*)()) 0x8076dd5; break; case 'FEPB': // Emerald French gSaveBlock1 = (pSaveBlock1) 0x2025A00; @@ -197,6 +300,7 @@ int main(void) { loadsave = (void(*)(char)) 0x815319d; mainloop = (void(*)()) 0x800042b; titlemid = 0x816fedd; + load_pokemon = (void(*)()) 0x8076dd1; break; case 'IEPB': // Emerald Italian gSaveBlock1 = (pSaveBlock1) 0x2025A00; @@ -206,6 +310,7 @@ int main(void) { loadsave = (void(*)(char)) 0x8153065; mainloop = (void(*)()) 0x800042b; titlemid = 0x816fda5; + load_pokemon = (void(*)()) 0x8076dd5; break; case 'SEPB': // Emerald Spanish gSaveBlock1 = (pSaveBlock1) 0x2025A00; @@ -215,6 +320,7 @@ int main(void) { loadsave = (void(*)(char)) 0x8153175; mainloop = (void(*)()) 0x800042b; titlemid = 0x816feb5; + load_pokemon = (void(*)()) 0x8076dd1; break; case 'EEPB': // Emerald English gSaveBlock1 = (pSaveBlock1) 0x2025A00; @@ -224,6 +330,7 @@ int main(void) { loadsave = (void(*)(char)) 0x81534d1; mainloop = (void(*)()) 0x800042b; titlemid = 0x817014d; + load_pokemon = (void(*)()) 0x8076dd5; break; case 'JEPB': // Emerald Japanese gSaveBlock1 = (pSaveBlock1) 0x20256A4; @@ -233,14 +340,20 @@ int main(void) { loadsave = (void(*)(char)) 0x815340d; mainloop = (void(*)()) 0x800042b; titlemid = 0x816ff45; + load_pokemon = (void(*)()) 0x80767dd; break; default: return 0; // this game isn't supported } loadsave(0); // now the save is loaded, we can do what we want with the loaded blocks. + // first, we're going to want to decrypt the parts that are crypted, if applicable. + decrypt_save_structures(gSaveBlock1,gSaveBlock2,gSaveBlock3); // time to call the payload. payload(gSaveBlock1,gSaveBlock2,gSaveBlock3); + // Now, we better call the function that sets the pokemon-related stuff from the structure elements of the loaded save again. + // Just in case the payload did something with that. + load_pokemon(); // In FR/LG/Emerald, just returning to the game is unwise. // The game reloads the savefile. // In FR/LG, this is done at the title screen after setting ASLR/saveblock-crypto up. (probably because at initial save-load, SaveBlock3 ptr isn't set up lol) @@ -249,8 +362,8 @@ int main(void) { // Easiest way to do this is to call into the middle of the function we want, using an ASM wrapper to set up the stack. // Here goes... if (titlemid) { - // Function reserves an extra 4 bytes of stack space in FireRed, and none in Emerald. - call_into_middle_of_titlescreen_func(titlemid,((gamecode << 8) == 'EPB\x00' ? 0 : 4)); + // Function reserves an extra 4 bytes of stack space in FireRed/LeafGreen, and none in Emerald. + call_into_middle_of_titlescreen_func(titlemid,(GAME_EM ? 0 : 4)); } // Now we've done what we want, time to return to the game. // Can't just return, the game will reload the save. -- cgit 1.4.1 From fac6ea00fd51e5be1d99db44746ed046378a6c65 Mon Sep 17 00:00:00 2001 From: slipstream/RoL Date: Tue, 21 Feb 2017 20:53:34 +0000 Subject: Forgot to commit a file Added GAME_RUBY/GAME_SAPP/GAME_RS/GAME_FR/GAME_LG/GAME_FRLG/GAME_EM/LANG_JAPAN macros. --- gba/source/payload.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/gba/source/payload.h b/gba/source/payload.h index 3f9ca62..d95476c 100644 --- a/gba/source/payload.h +++ b/gba/source/payload.h @@ -9,4 +9,14 @@ #include "saveblocks.h" +#define GAME_RUBY (((*(u32*)(0x80000AC)) << 8) == 'VXA\x00') +#define GAME_SAPP (((*(u32*)(0x80000AC)) << 8) == 'PXA\x00') +#define GAME_RS ((GAME_RUBY) || (GAME_SAPP)) +#define GAME_FR (((*(u32*)(0x80000AC)) << 8) == 'RPB\x00') +#define GAME_LG (((*(u32*)(0x80000AC)) << 8) == 'GPB\x00') +#define GAME_FRLG ((GAME_FR) || (GAME_LG)) +#define GAME_EM (((*(u32*)(0x80000AC)) << 8) == 'EPB\x00') + +#define LANG_JAPAN ((*(u8*)(0x80000AF)) == 'J') + void payload(pSaveBlock1 SaveBlock1,pSaveBlock2 SaveBlock2,pSaveBlock3 SaveBlock3); \ No newline at end of file -- cgit 1.4.1 From a73a0f06e04ddac7d5a122d9de6aaa43c2cc06f4 Mon Sep 17 00:00:00 2001 From: slipstream/RoL Date: Wed, 22 Feb 2017 02:28:26 +0000 Subject: update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4483a83..bee310a 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,10 @@ A GC and Wii homebrew app that sends a binary to the GBA using the different mul # Usage Have a GC Controller in Port 1 and a GBA with Gen3 game in Port 2. -Put your payload code to do some interesting stuff with the Pokémon game you have in `gba` dir. Example code that changes first character of player name to 'z' on Pokémon Ruby English (v1.0-1.2) provided. +Put your payload code to do some interesting stuff with the Pokémon game you have in `gba` dir. Example code that changes first character of player name to 'z' provided. Recompile, send to Wii or GC, turn on your GBA, hope that your code runs after the initial copyright screen of the game. (Code execution rate is for some reason not 100% reliable, PR to fix would be greatly appreciated. Sometimes KeyC derivation fails, sometimes GBA ignores the sent multiboot image, could be due to failure of a few different sends) # Acknowledgements Thanks to FIX94 for your multiboot game dumper, which the multiboot code is loosely based on (differences in crypto & protocol...) -Without it, this would have taken longer to do than the 2 days or so that it took. \ No newline at end of file +Without it, this would have taken longer to do than the 2 days or so that it took. -- cgit 1.4.1 From e37cc7de9583b00166e96a6632ba25edefff43ca Mon Sep 17 00:00:00 2001 From: slipstream/RoL Date: Sun, 26 Feb 2017 14:27:20 +0000 Subject: Major changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added saveblock structures. - Changed example payload, now it warps to Hall of Fame. - Added helper library for Pokémon manipulation, checksum calculation, etc. - Removed libSave, it's not needed. --- README.md | 2 +- gba/source/libSave.c | 596 ----------------------------------- gba/source/libSave.h | 27 -- gba/source/libpayload.c | 312 +++++++++++++++++++ gba/source/libpayload.h | 160 ++++++++++ gba/source/payload.c | 60 +++- gba/source/payload.h | 1 + gba/source/saveblocks.h | 338 +++++++++++++++++++- gba/source/savestructs.h | 793 +++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 1657 insertions(+), 632 deletions(-) delete mode 100644 gba/source/libSave.c delete mode 100644 gba/source/libSave.h create mode 100644 gba/source/libpayload.c create mode 100644 gba/source/libpayload.h create mode 100644 gba/source/savestructs.h diff --git a/README.md b/README.md index 4483a83..b833c50 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ A GC and Wii homebrew app that sends a binary to the GBA using the different mul # Usage Have a GC Controller in Port 1 and a GBA with Gen3 game in Port 2. -Put your payload code to do some interesting stuff with the Pokémon game you have in `gba` dir. Example code that changes first character of player name to 'z' on Pokémon Ruby English (v1.0-1.2) provided. +Put your payload code to do some interesting stuff with the Pokémon game you have in `gba` dir. Example code that warps the character to the Hall of Fame, adding a Bad Egg if the save has no Pokémon provided. Recompile, send to Wii or GC, turn on your GBA, hope that your code runs after the initial copyright screen of the game. (Code execution rate is for some reason not 100% reliable, PR to fix would be greatly appreciated. Sometimes KeyC derivation fails, sometimes GBA ignores the sent multiboot image, could be due to failure of a few different sends) diff --git a/gba/source/libSave.c b/gba/source/libSave.c deleted file mode 100644 index e3bda1d..0000000 --- a/gba/source/libSave.c +++ /dev/null @@ -1,596 +0,0 @@ -/* - libSave - Cartridge backup memory save routines. To use, call the required - routine with a pointer to an appropriately sized array of data to - be read from or written to the cartridge. - Data types are from wintermute's gba_types.h libgba library. - Original file from SendSave by Chishm -*/ - -#include -#include -#include -#include - -#define EEPROM_ADDRESS (0xDFFFF00) -#define REG_EEPROM *(vu16 *)(EEPROM_ADDRESS) -#define REG_DMA3CNT_H *(vu16 *)(REG_BASE + 0x0de) -#define REG_WAITCNT *(vu16 *)(REG_BASE + 0x204) - -//----------------------------------------------------------------------- -// Common EEPROM Routines -//----------------------------------------------------------------------- - -void EEPROM_SendPacket( u16* packet, int size ) -{ - REG_WAITCNT = (REG_WAITCNT & 0xF8FF) | 0x0300; - REG_DMA3SAD = (u32)packet; - REG_DMA3DAD = EEPROM_ADDRESS; - REG_DMA3CNT = 0x80000000 + size; - while((REG_DMA3CNT_H & 0x8000) != 0) ; -} - -void EEPROM_ReceivePacket( u16* packet, int size ) -{ - REG_WAITCNT = (REG_WAITCNT & 0xF8FF) | 0x0300; - REG_DMA3SAD = EEPROM_ADDRESS; - REG_DMA3DAD = (u32)packet; - REG_DMA3CNT = 0x80000000 + size; - while((REG_DMA3CNT_H & 0x8000) != 0) ; -} - -//----------------------------------------------------------------------- -// Routines for 512B EEPROM -//----------------------------------------------------------------------- - -void EEPROM_Read_512B( volatile u8 offset, u8* dest ) // dest must point to 8 bytes -{ - u16 packet[68]; - u8* out_pos; - u16* in_pos; - u8 out_byte; - int byte, bit; - - memset( packet, 0, 68*2 ); - - // Read request - packet[0] = 1; - packet[1] = 1; - - // 6 bits eeprom address (MSB first) - packet[2] = (offset>>5)&1; - packet[3] = (offset>>4)&1; - packet[4] = (offset>>3)&1; - packet[5] = (offset>>2)&1; - packet[6] = (offset>>1)&1; - packet[7] = (offset)&1; - - // End of request - packet[8] = 0; - - // Do transfers - EEPROM_SendPacket( packet, 9 ); - memset( packet, 0, 68*2 ); - EEPROM_ReceivePacket( packet, 68 ); - - // Extract data - in_pos = &packet[4]; - out_pos = dest; - for( byte = 7; byte >= 0; --byte ) - { - out_byte = 0; - for( bit = 7; bit >= 0; --bit ) - { -// out_byte += (*in_pos++)<>5)&1; - packet[3] = (offset>>4)&1; - packet[4] = (offset>>3)&1; - packet[5] = (offset>>2)&1; - packet[6] = (offset>>1)&1; - packet[7] = (offset)&1; - - // Extract data - in_pos = source; - out_pos = &packet[8]; - for( byte = 7; byte >= 0; --byte ) - { - in_byte = *in_pos++; - for( bit = 7; bit >= 0; --bit ) - { - *out_pos++ = (in_byte>>bit)&1; - } - } - - // End of request - packet[72] = 0; - - // Do transfers - EEPROM_SendPacket( packet, 73 ); - - // Wait for EEPROM to finish (should timeout after 10 ms) - while( (REG_EEPROM & 1) == 0 ); -} - -//--------------------------------------------------------------------------------- -void GetSave_EEPROM_512B(u8* data) -//--------------------------------------------------------------------------------- -{ - volatile u8 x; - u32 sleep; - - for (x=0;x<64;++x){ - EEPROM_Read_512B(x,&data[x*8]); - for(sleep=0;sleep<512000;sleep++); - } -} - -//--------------------------------------------------------------------------------- -void PutSave_EEPROM_512B(u8* data) -//--------------------------------------------------------------------------------- -{ - volatile u8 x; - u32 sleep; - - for (x=0;x<64;x++){ - EEPROM_Write_512B(x,&data[x*8]); - for(sleep=0;sleep<512000;sleep++); - } -} -//----------------------------------------------------------------------- -// Routines for 8KB EEPROM -//----------------------------------------------------------------------- - -void EEPROM_Read_8KB( volatile u16 offset, u8* dest ) // dest must point to 8 bytes -{ - u16 packet[68]; - u8* out_pos; - u16* in_pos; - u8 out_byte; - int byte, bit; - - memset( packet, 0, 68*2 ); - - // Read request - packet[0] = 1; - packet[1] = 1; - - // 14 bits eeprom address (MSB first) - packet[2] = (offset>>13)&1; - packet[3] = (offset>>12)&1; - packet[4] = (offset>>11)&1; - packet[5] = (offset>>10)&1; - packet[6] = (offset>>9)&1; - packet[7] = (offset>>8)&1; - packet[8] = (offset>>7)&1; - packet[9] = (offset>>6)&1; - packet[10] = (offset>>5)&1; - packet[11] = (offset>>4)&1; - packet[12] = (offset>>3)&1; - packet[13] = (offset>>2)&1; - packet[14] = (offset>>1)&1; - packet[15] = (offset)&1; - - // End of request - packet[16] = 0; - - // Do transfers - EEPROM_SendPacket( packet, 17 ); - memset( packet, 0, 68*2 ); - EEPROM_ReceivePacket( packet, 68 ); - - // Extract data - in_pos = &packet[4]; - out_pos = dest; - for( byte = 7; byte >= 0; --byte ) - { - out_byte = 0; - for( bit = 7; bit >= 0; --bit ) - { -// out_byte += (*in_pos++)<>13)&1; - packet[3] = (offset>>12)&1; - packet[4] = (offset>>11)&1; - packet[5] = (offset>>10)&1; - packet[6] = (offset>>9)&1; - packet[7] = (offset>>8)&1; - packet[8] = (offset>>7)&1; - packet[9] = (offset>>6)&1; - packet[10] = (offset>>5)&1; - packet[11] = (offset>>4)&1; - packet[12] = (offset>>3)&1; - packet[13] = (offset>>2)&1; - packet[14] = (offset>>1)&1; - packet[15] = (offset)&1; - - // Extract data - in_pos = source; - out_pos = &packet[16]; - for( byte = 7; byte >= 0; --byte ) - { - in_byte = *in_pos++; - for( bit = 7; bit >= 0; --bit ) - { - *out_pos++ = (in_byte>>bit)&1; - } - } - - // End of request - packet[80] = 0; - - // Do transfers - EEPROM_SendPacket( packet, 81 ); - - // Wait for EEPROM to finish (should timeout after 10 ms) - while( (REG_EEPROM & 1) == 0 ); -} - -//--------------------------------------------------------------------------------- -void GetSave_EEPROM_8KB(u8* data) -//--------------------------------------------------------------------------------- -{ - volatile u16 x; - u32 sleep; - - for (x=0;x<1024;x++){ - EEPROM_Read_8KB(x,&data[x*8]); - for(sleep=0;sleep<512000;sleep++); - } - -} - -//--------------------------------------------------------------------------------- -void PutSave_EEPROM_8KB(u8* data) -//--------------------------------------------------------------------------------- -{ - volatile u16 x; - u32 sleep; - - for (x=0;x<1024;x++){ - EEPROM_Write_8KB(x,&data[x*8]); - for(sleep=0;sleep<512000;sleep++); - } -} - -//--------------------------------------------------------------------------------- -void GetSave_SRAM_32KB(u8* data) -//--------------------------------------------------------------------------------- -{ - volatile u8 *sram= (u8*) 0x0E000000; - volatile u16 x; - - for (x = 0; x < 0x8000; ++x) - { - data[x] = sram[x]; - } - -} - -//--------------------------------------------------------------------------------- -void PutSave_SRAM_32KB(u8* data) -//--------------------------------------------------------------------------------- -{ - volatile u8 *sram= (u8*) 0x0E000000; - volatile u16 x; - - for (x = 0; x < 0x8000; ++x) - { - sram[x] = data[x]; - } -} - -//--------------------------------------------------------------------------------- -void GetSave_FLASH_64KB(u8* data) -//--------------------------------------------------------------------------------- -{ - volatile u8 *sram= (u8*) 0x0E000000; - volatile u32 x; - - for (x = 0; x < 0x10000; ++x) - { - data[x] = sram[x]; - } -} - -//--------------------------------------------------------------------------------- -void PutSave_FLASH_64KB(u8* foo) -//--------------------------------------------------------------------------------- -{ - volatile u8 *fctrl0 = (u8*) 0xE005555; - volatile u8 *fctrl1 = (u8*) 0xE002AAA; - volatile u8 *fctrl2 = (u8*) 0xE000000; - - //init flash - *fctrl0 = 0xAA; - *fctrl1 = 0x55; - *fctrl0 = 0x90; - *fctrl2 = 0xF0; - - //erase chip - *fctrl0 = 0xAA; - *fctrl1 = 0x55; - *fctrl0 = 0x80; - *fctrl0 = 0xAA; - *fctrl1 = 0x55; - *fctrl0 = 0x10; - - //wait for erase done - u8 val1; - u8 val2; - val1 = *fctrl2; - val2 = *fctrl2; - while (val1 != val2) { - val1 = *fctrl2; - val2 = *fctrl2; - } - val1 = *fctrl2; - val2 = *fctrl2; - while (val1 != val2) { - val1 = *fctrl2; - val2 = *fctrl2; - } - - volatile u8 *data = fctrl2; - u32 i; - //write data - for (i=0; i<65536; i++) { - *fctrl0 = 0xAA; - *fctrl1 = 0x55; - *fctrl0 = 0xA0; - data [i] = foo [ i ]; - val1 = data [ i ]; - val2 = data [ i ]; - - while (val1 != val2) { - val1 = data [ i ]; - val2 = data [ i ]; - } - val1 = data [ i ]; - val2 = data [ i ]; - while (val1 != val2) { - val1 = data [ i ]; - val2 = data [ i ]; - } - val1 = data [ i ]; - val2 = data [ i ]; - while (val1 != val2) { - val1 = data [ i ]; - val2 = data [ i ]; - } - } -} - -//--------------------------------------------------------------------------------- -void GetSave_FLASH_128KB(u8* data) -//--------------------------------------------------------------------------------- -{ - const u32 size = 0x10000; - - volatile u8 *fctrl0 = (u8*) 0xE005555; - volatile u8 *fctrl1 = (u8*) 0xE002AAA; - volatile u8 *fctrl2 = (u8*) 0xE000000; - volatile u32 i; - volatile u8 *sram= (u8*) 0x0E000000; - - //init flash - *fctrl0 = 0xAA; - *fctrl1 = 0x55; - *fctrl0 = 0x90; - *fctrl2 = 0xF0; - - // read first bank - *fctrl0 = 0xAA; - *fctrl1 = 0x55; - *fctrl0 = 0xB0; - *fctrl2 = 0x00; - - for (i=0; i=0;x--){ - switch (pak[x]) { - case 0x53414C46: - if (pak[x+1] == 0x5F4D3148){ - return 0x20000; // FLASH_128KB - } else if ((pak[x+1] & 0x0000FFFF) == 0x00005F48){ - return 0x10000; // FLASH_64KB - } else if (pak[x+1] == 0x32313548){ - return 0x10000; // FLASH_64KB - } - break; - case 0x52504545: - if ((pak[x+1] & 0x00FFFFFF) == 0x005F4D4F){ - GetSave_EEPROM_8KB(data); - for(i = 8; i < 0x800; i += 8) { - if(memcmp(data, data+i, 8) != 0) - return 0x2000; // EEPROM_8KB - } - return 0x200; // EEPROM_512B - } - break; - case 0x4D415253: - if ((pak[x+1] & 0x000000FF) == 0x0000005F){ - return 0x8000; // SRAM_32KB - } - } - } - return 0; -} - diff --git a/gba/source/libSave.h b/gba/source/libSave.h deleted file mode 100644 index 5ecf822..0000000 --- a/gba/source/libSave.h +++ /dev/null @@ -1,27 +0,0 @@ - - -//--------------------------------------------------------------------------------- -#ifdef __cplusplus -extern "C" { -#endif -//--------------------------------------------------------------------------------- - -void GetSave_EEPROM_512B(u8* data); -void PutSave_EEPROM_512B(u8* data); -void GetSave_EEPROM_8KB(u8* data); -void PutSave_EEPROM_8KB(u8* data); -void GetSave_SRAM_32KB(u8* data); -void PutSave_SRAM_32KB(u8* data); -void GetSave_FLASH_64KB(u8* data); -void PutSave_FLASH_64KB(u8* foo); -void GetSave_FLASH_128KB(u8* data); -void PutSave_FLASH_128KB(u8* foo); - -u32 SaveSize(u8* data, s32 gamesize); - - -//--------------------------------------------------------------------------------- -#ifdef __cplusplus -} // extern "C" -#endif -//--------------------------------------------------------------------------------- diff --git a/gba/source/libpayload.c b/gba/source/libpayload.c new file mode 100644 index 0000000..f13a34f --- /dev/null +++ b/gba/source/libpayload.c @@ -0,0 +1,312 @@ +/* + * Example Gen3-multiboot payload by slipstream/RoL 2017. + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + * + * libpayload.c: contains helper functions for the payload + */ + +#include +#include "payload.h" + +// private functions +static void CryptBoxPokemon(struct BoxPokemon* pkm); +static u16 crc16(const u8* data, u16 len); +static u32 CalculateRamScriptDataChecksum(struct RamScriptData* ramScript); + +/** + * Decrypts the substructures of a Pokémon structure, so they can be viewed or modified easily. + * Remember to call EncryptPokemon() afterwards. + * @param struct Pokemon* pkm The Pokémon to decrypt the substructures of. +*/ +void DecryptPokemon(struct Pokemon* pkm) { + struct BoxPokemon* boxMon = &(pkm->box); + CryptBoxPokemon(boxMon); +} + +/** + * Private function that does the actual work of crypting the substructures of a BoxPokemon. + * @param struct BoxPokemon* pkm The BoxPokemon whose substructures will be crypted. +*/ +static void CryptBoxPokemon(struct BoxPokemon* pkm) { + for (u32 i = 0; i < 12; i++) { + pkm->secure.raw[i] ^= (pkm->otId ^ pkm->personality); + } +} + +/** + * Decrypts the substructures of a core Pokémon structure, so they can be viewed or modified easily. + * Used by DecryptPokemon(). + * Remember to call EncryptPokemon() afterwards. + * @param struct BoxPokemon* pkm The BoxPokemon to decrypt the substructures of. +*/ +void DecryptBoxPokemon(struct BoxPokemon* pkm) { + CryptBoxPokemon(pkm); +} + +/** + * Encrypts the substructures of a Pokémon structure, and fixes the checksum. + * Must be used after DecryptPokemon() has been called, otherwise the Pokémon you decrypted and forgot to re-encrypt will become a Bad Egg. + * @param struct Pokemon* pkm The Pokémon to encrypt the substructures and fix the checksum of. +*/ +void EncryptPokemon(struct Pokemon* pkm) { + struct BoxPokemon* boxMon = &(pkm->box); + EncryptBoxPokemon(boxMon); +} + +/** + * Encrypts the substructures of a core Pokémon structure, and fixes the checksum. + * Must be used after DecryptBoxPokemon() has been called, otherwise the Pokémon you decrypted and forgot to re-encrypt will become a Bad Egg. + * @param struct BoxPokemon* pkm The BoxPokemon to encrypt the substructures and fix the checksum of. +*/ +void EncryptBoxPokemon(struct BoxPokemon* pkm) { + FixBoxPokemonChecksum(pkm); + CryptBoxPokemon(pkm); +} + +/** + * Gets a substructure of a Pokémon structure. + * Call DecryptPokemon() first or the substructure data will be encrypted. + * @param struct Pokemon* pkm The Pokemon to get a substructure of. + * @param u8 substructId The substructure to get. + * @return union PokemonSubstruct* The substructure. +*/ +union PokemonSubstruct* GetPokemonSubstruct(struct Pokemon* pkm,u8 substructId) { + struct BoxPokemon* boxMon = &(pkm->box); + return GetBoxPokemonSubstruct(boxMon,substructId); +} + +/** + * Gets a substructure of a core Pokémon structure. + * Call DecryptBoxPokemon() first or the substructure data will be encrypted. + * @param struct Pokemon* pkm The Pokemon to get a substructure of. + * @param u8 substructId The substructure to get. + * @return union PokemonSubstruct* The substructure. +*/ +union PokemonSubstruct* GetBoxPokemonSubstruct(struct BoxPokemon* pkm,u8 substructId) { + if (substructId > 3) return NULL; + u32 personality = pkm->personality; + // Staring at the substruct indexes, I noticed the last two columns are the reverse of the first two! + // that is, substructId==2 column is the reverse of substructId==1, substructId==3 is the reverse of substructId==0 + // At least that means there's no need to hardcode all four. + u8 substruct_idxes[2][24] = { + { 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 2, 3, 1, 1, 2, 3, 2, 3, 1, 1, 2, 3, 2, 3 }, + { 1, 1, 2, 3, 2, 3, 0, 0, 0, 0, 0, 0, 2, 3, 1, 1, 3, 2, 2, 3, 1, 1, 3, 2 } + }; + u32 modulo = (personality % 24); + if (substructId < 2) { + return &( pkm->secure.substructs[ substruct_idxes[substructId][modulo] ] ); + } + return &( pkm->secure.substructs[ substruct_idxes[3 - substructId][23 - modulo] ] ); +} + +/** + * Gets the checksum of a core Pokémon structure. + * @param struct BoxPokemon* pkm The BoxPokemon to calculate the checksum of. + * @return u16 The checksum. +*/ +u16 CalculateBoxPokemonChecksum(struct BoxPokemon* pkm) { + u16 checksum = 0; + union PokemonSubstruct* substructs[4] = { + GetBoxPokemonSubstruct(pkm,0), + GetBoxPokemonSubstruct(pkm,1), + GetBoxPokemonSubstruct(pkm,2), + GetBoxPokemonSubstruct(pkm,3) + }; + + for (int substruct = 0; substruct < 4; substruct++) { + for (int i = 0; i < 6; i++) { + checksum += substructs[substruct]->raw[i]; + } + } + return checksum; +} + +/** + * Fixes the checksum of a core Pokémon structure. + * @param struct BoxPokemon* pkm The BoxPokemon to fix the checksum of. +*/ +void FixBoxPokemonChecksum(struct BoxPokemon* pkm) { + pkm->checksum = CalculateBoxPokemonChecksum(pkm); +} + +/** + * Gets the zeroth substructure ("Growth") of a Pokémon structure. + * Call DecryptPokemon() first or the substructure data will be encrypted. + * @param struct Pokemon* pkm The Pokémon to get a substructure of. + * @return struct PokemonSubstruct0* The substructure. +*/ +struct PokemonSubstruct0* GetPokemonSubstruct0(struct Pokemon* pkm) { + struct BoxPokemon* boxMon = &(pkm->box); + return GetBoxPokemonSubstruct0(boxMon); +} + +/** + * Gets the zeroth substructure ("Growth") of a core Pokémon structure. + * Call DecryptBoxPokemon() first or the substructure data will be encrypted. + * @param struct BoxPokemon* pkm The BoxPokemon to get the substructure of. + * @return struct PokemonSubstruct0* The substructure. +*/ +struct PokemonSubstruct0* GetBoxPokemonSubstruct0(struct BoxPokemon* pkm) { + return &( GetBoxPokemonSubstruct(pkm,0)->type0 ); +} + +/** + * Gets the first substructure ("Attacks") of a Pokémon structure. + * Call DecryptPokemon() first or the substructure data will be encrypted. + * @param struct Pokemon* pkm The Pokémon to get a substructure of. + * @return struct PokemonSubstruct0* The substructure. +*/ +struct PokemonSubstruct1* GetPokemonSubstruct1(struct Pokemon* pkm) { + struct BoxPokemon* boxMon = &(pkm->box); + return GetBoxPokemonSubstruct1(boxMon); +} + +/** + * Gets the first substructure ("Attacks") of a core Pokémon structure. + * Call DecryptBoxPokemon() first or the substructure data will be encrypted. + * @param struct BoxPokemon* pkm The BoxPokemon to get the substructure of. + * @return struct PokemonSubstruct1* The substructure. +*/ +struct PokemonSubstruct1* GetBoxPokemonSubstruct1(struct BoxPokemon* pkm) { + return &( GetBoxPokemonSubstruct(pkm,1)->type1 ); +} + +/** + * Gets the second substructure ("EVs & Condition") of a Pokémon structure. + * Call DecryptPokemon() first or the substructure data will be encrypted. + * @param struct Pokemon* pkm The Pokémon to get a substructure of. + * @return struct PokemonSubstruct0* The substructure. +*/ +struct PokemonSubstruct2* GetPokemonSubstruct2(struct Pokemon* pkm) { + struct BoxPokemon* boxMon = &(pkm->box); + return GetBoxPokemonSubstruct2(boxMon); +} + +/** + * Gets the second substructure ("EVs & Condition") of a core Pokémon structure. + * Call DecryptBoxPokemon() first or the substructure data will be encrypted. + * @param struct BoxPokemon* pkm The BoxPokemon to get the substructure of. + * @return struct PokemonSubstruct2* The substructure. +*/ +struct PokemonSubstruct2* GetBoxPokemonSubstruct2(struct BoxPokemon* pkm) { + return &( GetBoxPokemonSubstruct(pkm,2)->type2 ); +} + +/** + * Gets the third substructure ("Miscellaneous") of a Pokémon structure. + * Call DecryptPokemon() first or the substructure data will be encrypted. + * @param struct Pokemon* pkm The Pokémon to get a substructure of. + * @return struct PokemonSubstruct0* The substructure. +*/ +struct PokemonSubstruct3* GetPokemonSubstruct3(struct Pokemon* pkm) { + struct BoxPokemon* boxMon = &(pkm->box); + return GetBoxPokemonSubstruct3(boxMon); +} + +/** + * Gets the third substructure ("Miscellaneous") of a core Pokémon structure. + * Call DecryptBoxPokemon() first or the substructure data will be encrypted. + * @param struct BoxPokemon* pkm The BoxPokemon to get the substructure of. + * @return struct PokemonSubstruct3* The substructure. +*/ +struct PokemonSubstruct3* GetBoxPokemonSubstruct3(struct BoxPokemon* pkm) { + return &( GetBoxPokemonSubstruct(pkm,3)->type3 ); +} + +/** + * Calculates the checksum for an R/S-specific Enigma Berry structure in SaveBlock1. + * @param struct EnigmaBerry* enigmaBerry The R/S-specific Enigma Berry to calculate the checksum for. + * @return u32 The checksum. +*/ +u32 CalculateEnigmaBerryChecksumRS(struct EnigmaBerry* enigmaBerry) { + if (!GAME_RS) return 0; // Enigma Berry structure changed in FR/LG, use CalculateEnigmaBerryChecksumFRLGE() instead. + u32 checksum = 0; + // Save off and zero out the original Enigma Berry description pointers. + const u8* description[2] = { enigmaBerry->berry.description1, enigmaBerry->berry.description2 }; + enigmaBerry->berry.description1 = enigmaBerry->berry.description2 = NULL; + u8* enigmaBerryBytes = (u8*)enigmaBerry; + // Calculate the checksum. + for (u32 i = 0; i < 1324; i++) { + checksum += enigmaBerryBytes[i]; + } + // Restore the saved description. + enigmaBerry->berry.description1 = description[0]; + enigmaBerry->berry.description2 = description[1]; + return checksum; +} + +/** + * Calculates the checksum for an FR/LG/Emerald-specific Enigma Berry structure in SaveBlock1. + * @param struct EnigmaBerryFRLGE* enigmaBerry The FR/LG/Emerald-specific Enigma Berry to calculate the checksum for. + * @return u32 The checksum. +*/ +u32 CalculateEnigmaBerryChecksumFRLGE(struct EnigmaBerryFRLGE* enigmaBerry) { + if (GAME_RS) return 0; // Enigma Berry structure is different in R/S, use CalculateEnigmaBerryChecksumRS() instead. + u32 checksum = 0; + u8* enigmaBerryBytes = (u8*)enigmaBerry; + for (int i = 0; i < 0x30; i++) { + checksum += enigmaBerryBytes[i]; + } + return checksum; +} + +/** + * Calculates the checksum for an unspecified Enigma Berry structure in SaveBlock1. (detected by current game) + * @param void* enigmaBerry The Enigma Berry structure to calculate the checksum for. + * @return u32 The checksum. + */ +u32 CalculateEnigmaBerryChecksum(void* enigmaBerry) { + if (GAME_RS) return CalculateEnigmaBerryChecksumRS( (struct EnigmaBerry*) enigmaBerry ); + return CalculateEnigmaBerryChecksumFRLGE( (struct EnigmaBerryFRLGE*) enigmaBerry ); +} + +/** + * Calculates the checksum for a RAM script structure in SaveBlock1. + * @param struct RamScript* ramScript The RAM script structure to calculate the checksum for. + * @return u32 The checksum. + */ +u32 CalculateRamScriptChecksum(struct RamScript* ramScript) { + asm(""); // make sure the call to CalculateRamScriptDataChecksum() is *not* inlined. + // For some reason, if it gets inlined, something happens, and the wrong length is used when checksumming... + return CalculateRamScriptDataChecksum(&ramScript->data); +} + +/** + * Calculates the checksum for a RAM script structure in SaveBlock1. + * @param struct RamScript* ramScript The RAM script structure to calculate the checksum for. + * @return u32 The checksum. + */ +static __attribute__ ((noinline)) u32 CalculateRamScriptDataChecksum(struct RamScriptData* ramScript) { + u32 checksum = 0; + u32 i = 0; + u8* ramScriptBytes = (u8*)(ramScript); + if (GAME_RS) { + for (i = 0; i < sizeof(struct RamScriptData); i++) checksum += ramScriptBytes[i]; + return checksum; + } + + return (u32)crc16(ramScriptBytes,sizeof(struct RamScriptData) + 1); +} + +/** + * Private function to CRC-16, (used by FR/LG/Emerald ramscript checksum). Adapted from http://forums.glitchcity.info/index.php?topic=7114.0 + * @param u8* Data to checksum + * @param u16 Length of data + * @return u16 The checksum +*/ +static u16 crc16(const u8* data, u16 len) { + u16 crc = 0x1121; + for (u16 i = 0; i < len; ++i) { + crc ^= data[i]; + for (u16 j = 0; j < 8; ++j) { + if(crc & 1) { + crc = (crc >> 1) ^ 0x8408; + } else { + crc >>= 1; + } + } + } + return ~crc; +} \ No newline at end of file diff --git a/gba/source/libpayload.h b/gba/source/libpayload.h new file mode 100644 index 0000000..4be6fb3 --- /dev/null +++ b/gba/source/libpayload.h @@ -0,0 +1,160 @@ +/* + * Example Gen3-multiboot payload by slipstream/RoL 2017. + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + * + * libpayload.h: header for payload helper functions + */ + +/** + * Decrypts the substructures of a Pokémon structure, so they can be viewed or modified easily. + * Remember to call EncryptPokemon() afterwards. + * @param struct Pokemon* pkm The Pokémon to decrypt the substructures of. +*/ +void DecryptPokemon(struct Pokemon* pkm); + +/** + * Decrypts the substructures of a core Pokémon structure, so they can be viewed or modified easily. + * Used by DecryptPokemon(). + * Remember to call EncryptPokemon() afterwards. + * @param struct BoxPokemon* pkm The BoxPokemon to decrypt the substructures of. +*/ +void DecryptBoxPokemon(struct BoxPokemon* pkm); + +/** + * Encrypts the substructures of a Pokémon structure, and fixes the checksum. + * Must be used after DecryptPokemon() has been called, otherwise the Pokémon you decrypted and forgot to re-encrypt will become a Bad Egg. + * @param struct Pokemon* pkm The Pokémon to encrypt the substructures and fix the checksum of. +*/ +void EncryptPokemon(struct Pokemon* pkm); + +/** + * Encrypts the substructures of a core Pokémon structure, and fixes the checksum. + * Must be used after DecryptBoxPokemon() has been called, otherwise the Pokémon you decrypted and forgot to re-encrypt will become a Bad Egg. + * @param struct BoxPokemon* pkm The BoxPokemon to encrypt the substructures and fix the checksum of. +*/ +void EncryptBoxPokemon(struct BoxPokemon* pkm); + +/** + * Gets a substructure of a Pokémon structure. + * Call DecryptPokemon() first or the substructure data will be encrypted. + * @param struct Pokemon* pkm The Pokemon to get a substructure of. + * @param u8 substructId The substructure to get. + * @return union PokemonSubstruct* The substructure. +*/ +union PokemonSubstruct* GetPokemonSubstruct(struct Pokemon* pkm,u8 substructId); + +/** + * Gets a substructure of a core Pokémon structure. + * Call DecryptBoxPokemon() first or the substructure data will be encrypted. + * @param struct Pokemon* pkm The Pokemon to get a substructure of. + * @param u8 substructId The substructure to get. + * @return union PokemonSubstruct* The substructure. +*/ +union PokemonSubstruct* GetBoxPokemonSubstruct(struct BoxPokemon* pkm,u8 substructId); + +/** + * Gets the checksum of a core Pokémon structure. + * @param struct BoxPokemon* pkm The BoxPokemon to calculate the checksum of. + * @return u16 The checksum. +*/ +u16 CalculateBoxPokemonChecksum(struct BoxPokemon* pkm); + +/** + * Fixes the checksum of a core Pokémon structure. + * @param struct BoxPokemon* pkm The BoxPokemon to fix the checksum of. +*/ +void FixBoxPokemonChecksum(struct BoxPokemon* pkm); + +/** + * Gets the zeroth substructure ("Growth") of a Pokémon structure. + * Call DecryptPokemon() first or the substructure data will be encrypted. + * @param struct Pokemon* pkm The Pokémon to get a substructure of. + * @return struct PokemonSubstruct0* The substructure. +*/ +struct PokemonSubstruct0* GetPokemonSubstruct0(struct Pokemon* pkm); + +/** + * Gets the zeroth substructure ("Growth") of a core Pokémon structure. + * Call DecryptBoxPokemon() first or the substructure data will be encrypted. + * @param struct BoxPokemon* pkm The BoxPokemon to get the substructure of. + * @return struct PokemonSubstruct0* The substructure. +*/ +struct PokemonSubstruct0* GetBoxPokemonSubstruct0(struct BoxPokemon* pkm); + +/** + * Gets the first substructure ("Attacks") of a Pokémon structure. + * Call DecryptPokemon() first or the substructure data will be encrypted. + * @param struct Pokemon* pkm The Pokémon to get a substructure of. + * @return struct PokemonSubstruct0* The substructure. +*/ +struct PokemonSubstruct1* GetPokemonSubstruct1(struct Pokemon* pkm); + +/** + * Gets the first substructure ("Attacks") of a core Pokémon structure. + * Call DecryptBoxPokemon() first or the substructure data will be encrypted. + * @param struct BoxPokemon* pkm The BoxPokemon to get the substructure of. + * @return struct PokemonSubstruct1* The substructure. +*/ +struct PokemonSubstruct1* GetBoxPokemonSubstruct1(struct BoxPokemon* pkm); + +/** + * Gets the second substructure ("EVs & Condition") of a Pokémon structure. + * Call DecryptPokemon() first or the substructure data will be encrypted. + * @param struct Pokemon* pkm The Pokémon to get a substructure of. + * @return struct PokemonSubstruct0* The substructure. +*/ +struct PokemonSubstruct2* GetPokemonSubstruct2(struct Pokemon* pkm); + +/** + * Gets the second substructure ("EVs & Condition") of a core Pokémon structure. + * Call DecryptBoxPokemon() first or the substructure data will be encrypted. + * @param struct BoxPokemon* pkm The BoxPokemon to get the substructure of. + * @return struct PokemonSubstruct2* The substructure. +*/ +struct PokemonSubstruct2* GetBoxPokemonSubstruct2(struct BoxPokemon* pkm); + +/** + * Gets the third substructure ("Miscellaneous") of a Pokémon structure. + * Call DecryptPokemon() first or the substructure data will be encrypted. + * @param struct Pokemon* pkm The Pokémon to get a substructure of. + * @return struct PokemonSubstruct0* The substructure. +*/ +struct PokemonSubstruct3* GetPokemonSubstruct3(struct Pokemon* pkm); + +/** + * Gets the third substructure ("Miscellaneous") of a core Pokémon structure. + * Call DecryptBoxPokemon() first or the substructure data will be encrypted. + * @param struct BoxPokemon* pkm The BoxPokemon to get the substructure of. + * @return struct PokemonSubstruct3* The substructure. +*/ +struct PokemonSubstruct3* GetBoxPokemonSubstruct3(struct BoxPokemon* pkm); + +/** + * Calculates the checksum for an R/S-specific Enigma Berry structure in SaveBlock1. + * @param struct EnigmaBerry* enigmaBerry The R/S-specific Enigma Berry to calculate the checksum for. + * @return u32 The checksum. +*/ +u32 CalculateEnigmaBerryChecksumRS(struct EnigmaBerry* enigmaBerry); + +/** + * Calculates the checksum for an FR/LG/Emerald-specific Enigma Berry structure in SaveBlock1. + * @param struct EnigmaBerryFRLGE* enigmaBerry The FR/LG/Emerald-specific Enigma Berry to calculate the checksum for. + * @return u32 The checksum. +*/ +u32 CalculateEnigmaBerryChecksumFRLGE(struct EnigmaBerryFRLGE* enigmaBerry); + +/** + * Calculates the checksum for an unspecified Enigma Berry structure in SaveBlock1. (detected by current game) + * @param void* enigmaBerry The Enigma Berry structure to calculate the checksum for. + * @return u32 The checksum. + */ +u32 CalculateEnigmaBerryChecksum(void* enigmaBerry); + +/** + * Calculates the checksum for a RAM script structure in SaveBlock1. + * @param struct RamScript* ramScript The RAM script structure to calculate the checksum for. + * @return u32 The checksum. + */ +u32 CalculateRamScriptChecksum(struct RamScript* ramScript); \ No newline at end of file diff --git a/gba/source/payload.c b/gba/source/payload.c index 7015774..665af70 100644 --- a/gba/source/payload.c +++ b/gba/source/payload.c @@ -12,7 +12,61 @@ // Your payload code should obviously go into the body of this, the payload function. void payload(pSaveBlock1 SaveBlock1,pSaveBlock2 SaveBlock2,pSaveBlock3 SaveBlock3) { - // This example payload will modify the first character of the player's name. - // It will change to 'z'. You can see the character encoding table here: http://bulbapedia.bulbagarden.net/wiki/Character_encoding_in_Generation_III - SaveBlock2[0] = 0xee; // 'z' + // HoF-warp example payload! + + // Structure offsets are different between R/S, FR/LG, and Emerald. + // The beginning of the structures are the same but do NOT take shortcuts here, it's not good practise. + // If you want to support multiple games, make specific checks for games, use the right structure for each game. + SaveBlock1_RS* sb1rs = &SaveBlock1->rs; + SaveBlock1_FRLG* sb1frlg = &SaveBlock1->frlg; + SaveBlock1_E* sb1e = &SaveBlock1->e; + if (GAME_FRLG) { + sb1frlg->location.mapGroup = 1; // generic indoors? + sb1frlg->location.mapNum = 80; // Hall of Fame + // set coords to the same place that the champions' room script sets them to + sb1frlg->location.x = sb1frlg->pos.x = 5; + sb1frlg->location.y = sb1frlg->pos.y = 12; + sb1frlg->mapDataId = 0xDA; // from HoF map-header + sb1frlg->location.warpId = 0xff; + // make sure the HoF script doesn't crash, which it will do if 0 pokémon + if (sb1frlg->playerPartyCount == 0) { + sb1frlg->playerPartyCount = 1; + // this isn't enough, the heal animation recalculates the party count ignoring empty spots + // so let's hack together one. i don't care about it becoming a bad egg at all. + sb1frlg->playerParty[0].box.personality++; + } + return; + } else if (GAME_RS) { + sb1rs->location.mapGroup = 16; // Ever Grande City + sb1rs->location.mapNum = 11; // Hall of Fame + // set coords to the same place that the champions' room script sets them to + sb1rs->location.x = sb1rs->pos.x = 7; + sb1rs->location.y = sb1rs->pos.y = 16; + sb1rs->mapDataId = 299; // from HoF map-header + sb1rs->location.warpId = 0xff; + // make sure the HoF script doesn't crash, which it will do if 0 pokémon + if (sb1rs->playerPartyCount == 0) { + sb1rs->playerPartyCount = 1; + // this isn't enough, the heal animation recalculates the party count ignoring empty spots + // so let's hack together one. i don't care about it becoming a bad egg at all. + sb1rs->playerParty[0].box.personality++; + } + return; + } else if (GAME_EM) { + sb1e->location.mapGroup = 16; // Ever Grande City + sb1e->location.mapNum = 11; // Hall of Fame + // set coords to the same place that the champions' room script sets them to + sb1e->location.x = sb1e->pos.x = 7; + sb1e->location.y = sb1e->pos.y = 16; + sb1e->mapDataId = 298; // from HoF map-header + sb1e->location.warpId = 0xff; + // make sure the HoF script doesn't crash, which it will do if 0 pokémon + if (sb1e->playerPartyCount == 0) { + sb1e->playerPartyCount = 1; + // this isn't enough, the heal animation recalculates the party count ignoring empty spots + // so let's hack together one. i don't care about it becoming a bad egg at all. + sb1e->playerParty[0].box.personality++; + } + return; + } } \ No newline at end of file diff --git a/gba/source/payload.h b/gba/source/payload.h index d95476c..ea87200 100644 --- a/gba/source/payload.h +++ b/gba/source/payload.h @@ -8,6 +8,7 @@ */ #include "saveblocks.h" +#include "libpayload.h" #define GAME_RUBY (((*(u32*)(0x80000AC)) << 8) == 'VXA\x00') #define GAME_SAPP (((*(u32*)(0x80000AC)) << 8) == 'PXA\x00') diff --git a/gba/source/saveblocks.h b/gba/source/saveblocks.h index 45c127c..cf0a5c3 100644 --- a/gba/source/saveblocks.h +++ b/gba/source/saveblocks.h @@ -7,9 +7,337 @@ * saveblocks.h: describes saveblock structures for all of Gen 3 (yay!) */ -// TODO: this entire file. Placeholders for now, fill in later, if I can be bothered. -// I don't really want to make a fork of pokeruby's headers for now... +// Most of the structures come from pokeruby, FR/LG changes come from my own research / the firered IDB on pokecommunity +#include "savestructs.h" -typedef u8 SaveBlock1, *pSaveBlock1; -typedef u8 SaveBlock2, *pSaveBlock2; -typedef u8 SaveBlock3, *pSaveBlock3; \ No newline at end of file +typedef struct +{ + /*0x00*/ struct Coords16 pos; + /*0x04*/ struct WarpData location; + /*0x0C*/ struct WarpData warp[4]; + /*0x2C*/ u16 battleMusic; + /*0x2E*/ u8 weather; + /*0x2F*/ u8 filler_2F; + /*0x30*/ u8 flashUsed; + /*0x32*/ u16 mapDataId; + /*0x34*/ u16 mapView[0x100]; + /*0x234*/ u8 playerPartyCount; + /*0x238*/ struct Pokemon playerParty[6]; + /*0x490*/ u32 money; + /*0x494*/ u16 coins; + /*0x496*/ u16 registeredItem; // registered for use with SELECT button + /*0x498*/ struct ItemSlot pcItems[50]; + /*0x560*/ struct ItemSlot bagPocket_Items[20]; + /*0x5B0*/ struct ItemSlot bagPocket_KeyItems[20]; + /*0x600*/ struct ItemSlot bagPocket_PokeBalls[16]; + /*0x640*/ struct ItemSlot bagPocket_TMHM[64]; + /*0x740*/ struct ItemSlot bagPocket_Berries[46]; + /*0x7F8*/ struct Pokeblock pokeblocks[40]; + /*0x938*/ u8 unk938[52]; // pokedex related + /*0x96C*/ u16 berryBlenderRecords[3]; + /*0x972*/ u8 filler_972[0x6]; + /*0x978*/ u16 trainerRematchStepCounter; + /*0x97A*/ u8 trainerRematches[100]; + /*0x9E0*/ struct MapObject mapObjects[16]; + /*0xC20*/ struct MapObjectTemplate mapObjectTemplates[64]; + /*0x1220*/ u8 flags[0x120]; + /*0x1340*/ u16 vars[0x100]; + /*0x1540*/ u32 gameStats[50]; + /*0x1608*/ struct BerryTree berryTrees[128]; + /*0x1A08*/ struct SecretBaseRecord secretBases[20]; + /*0x2688*/ u8 playerRoomDecor[12]; + /*0x2694*/ u8 playerRoomDecorPos[12]; + /*0x26A0*/ u8 decorDesk[10]; + /*0x26AA*/ u8 decorChair[10]; + /*0x26B4*/ u8 decorPlant[10]; + /*0x26BE*/ u8 decorOrnament[30]; + /*0x26DC*/ u8 decorMat[30]; + /*0x26FA*/ u8 decorPoster[10]; + /*0x2704*/ u8 decorDoll[40]; + /*0x272C*/ u8 decorCushion[10]; + /*0x2736*/ u8 padding_2736[2]; + /*0x2738*/ TVShow tvShows[24]; + /*0x2A98*/ u8 filler_2A98[0x64]; + /*0x2AFC*/ u16 outbreakPokemonSpecies; + /*0x2AFE*/ u8 outbreakLocationMapNum; + /*0x2AFF*/ u8 outbreakLocationMapGroup; + /*0x2B00*/ u8 outbreakPokemonLevel; + /*0x2B01*/ u8 outbreakUnk1; + /*0x2B02*/ u16 outbreakUnk2; + /*0x2B04*/ u16 outbreakPokemonMoves[4]; + /*0x2B0C*/ u8 outbreakUnk4; + /*0x2B0D*/ u8 outbreakPokemonProbability; + /*0x2B0E*/ u16 outbreakUnk5; + /*0x2B10*/ u8 filler_2B0E[0xC]; + /*0x2B1C*/ u16 unk2B1C[4]; + /*0x2B24*/ u8 filler_2B24[0x28]; + /*0x2B4C*/ struct MailStruct mail[16]; + /*0x2D8C*/ u8 filler_2D8C[0x8]; + /*0x2D94*/ OldMan oldMan; + /*0x2DC0*/ u8 unk_2DC0[0x14]; + /*0x2DD4*/ struct EasyChatPair easyChatPairs[5]; //Dewford trend [0] and some other stuff + /*0x2DFC*/ u8 filler_2DFC[0x100]; + /*0x2EFC*/ struct SB1_2EFC_Struct sb1_2EFC_struct[5]; + /*0x2F9C*/ u8 filler_2F9C[0xA0]; + /*0x303C*/ u8 filler_303C[0x38]; + /*0x3074*/ u8 filler_3074[0x42]; + /*0x30B6*/ u8 filler_30B6; + /*0x30B7*/ u8 filler_30B7[0x59]; + /*0x3110*/ u8 giftRibbons[7]; + /*0x3117*/ u8 filler_311B[0x2D]; + /*0x3144*/ struct Roamer roamer; + /*0x3158*/ u8 filler_3158[0x8]; + /*0x3160*/ struct EnigmaBerry enigmaBerry; // this is actually offset by 0x98 ... + /*0x3690*/ struct RamScript ramScript; + /*0x3A7C*/ u8 filler_3A7C[0x10]; + /*0x3A8C*/ u8 unk3A8C[52]; //pokedex related +} SaveBlock1_RS; + +typedef struct // Don't rely on the commented offsets, they'll be wrong due to elements removed in FR/LG... +{ + /*0x00*/ struct Coords16 pos; + /*0x04*/ struct WarpData location; + /*0x0C*/ struct WarpData warp[4]; + /*0x2C*/ u16 battleMusic; + /*0x2E*/ u8 weather; + /*0x2F*/ u8 filler_2F; + /*0x30*/ u8 flashUsed; + /*0x32*/ u16 mapDataId; +// /*0x34*/ u16 mapView[0x100]; // Not in fr/lg + /*0x234*/ u8 playerPartyCount; + /*0x238*/ struct Pokemon playerParty[6]; + /*0x490*/ u32 money; + /*0x494*/ u16 coins; + /*0x496*/ u16 registeredItem; // registered for use with SELECT button + /*0x498*/ struct ItemSlot pcItems[30]; + /*0x560*/ struct ItemSlot bagPocket_Items[42]; + /*0x5B0*/ struct ItemSlot bagPocket_KeyItems[30]; + /*0x600*/ struct ItemSlot bagPocket_PokeBalls[13]; + /*0x640*/ struct ItemSlot bagPocket_TMHM[58]; + /*0x740*/ struct ItemSlot bagPocket_Berries[43]; +// /*0x7F8*/ struct Pokeblock pokeblocks[40]; // Not in fr/lg + /*0x938*/ u8 unk938[52]; // pokedex related + /*0x96C*/ u8 unk_62C[12]; + /*0x972*/ u8 filler_972[0x6]; + /*0x97A*/ u8 unk_63E[98]; + /*0x9E0*/ struct MapObject mapObjects[16]; + /*0xC20*/ struct MapObjectTemplate mapObjectTemplates[64]; + /*0x1220*/ u8 flags[0x120]; + /*0x1340*/ u16 vars[0x100]; + /*0x1540*/ u32 gameStats[64]; // encrypted with saveblock2 xor-key + struct QuestStory questlog[4]; + u8 messages[12][4]; + struct NPCState npc_states[0x10]; + u8 unk_2f10[112]; + struct DaycarePokemon daycare[2]; + u8 unk_3098[56]; + struct Roamer roamer; + u8 unk_30e4[8]; + /*0x3160*/ struct EnigmaBerryFRLGE enigmaBerry; + u8 unk_3120[0x1C0]; // 4 bytes of CRC16, then 444 bytes of unknown. Mystery Gift related. + u8 unk_32E0[0x150]; // 4 bytes of CRC16, then 332 bytes of unknown. Mystery Gift related. "mevent_buffer_1" + u8 unk_3430[0x150]; // 4 bytes of CRC16, then 332 bytes of unknown. Mystery Gift related. "mevent_buffer_2" + u8 unk_368C[0x9C]; // padding? doesn't seem to be actually used + struct RamScript ramScript; + u8 unk_3A07[17]; + u8 pokemon_flags_2[52]; + u8 rivalName[8]; + u8 unk_3a54[128]; + u8 words[21][10]; + u8 unk_3ba6[570]; +} __attribute__((aligned(1))) SaveBlock1_FRLG; + +typedef struct // Don't rely on the commented offsets, they'll be wrong due to elements changed/added in Emerald... +{ + /*0x00*/ struct Coords16 pos; + /*0x04*/ struct WarpData location; + /*0x0C*/ struct WarpData warp[4]; + /*0x2C*/ u16 battleMusic; + /*0x2E*/ u8 weather; + /*0x2F*/ u8 filler_2F; + /*0x30*/ u8 flashUsed; + /*0x32*/ u16 mapDataId; + /*0x34*/ u16 mapView[0x100]; + /*0x234*/ u8 playerPartyCount; + /*0x238*/ struct Pokemon playerParty[6]; + /*0x490*/ u32 money; + /*0x494*/ u16 coins; + /*0x496*/ u16 registeredItem; // registered for use with SELECT button + /*0x498*/ struct ItemSlot pcItems[50]; + /*0x560*/ struct ItemSlot bagPocket_Items[30]; + /*0x5D8*/ struct ItemSlot bagPocket_KeyItems[30]; + /*0x650*/ struct ItemSlot bagPocket_PokeBalls[16]; + /*0x690*/ struct ItemSlot bagPocket_TMHM[64]; + /*0x790*/ struct ItemSlot bagPocket_Berries[46]; + /*0x7F8*/ struct Pokeblock pokeblocks[40]; // every offset is shifted by 0x50 from here on thanks to changed bag-counts + /*0x938*/ u8 unk938[52]; // pokedex related + /*0x96C*/ u16 berryBlenderRecords[3]; + /*0x972*/ u8 filler_972[0x6]; + /*0x978*/ u16 trainerRematchStepCounter; + /*0x97A*/ u8 trainerRematches[100]; + /*0x9E0*/ struct MapObject mapObjects[16]; + /*0xC20*/ struct MapObjectTemplate mapObjectTemplates[64]; + /*0x1220*/ u8 flags[0x12C]; + /*0x1340*/ u16 vars[0x100]; // offsets shifted by 0x5C from here on thanks to added flags + /*0x1540*/ u32 gameStats[64]; // encrypted with saveblock2 xor-key + /*0x1608*/ struct BerryTree berryTrees[128]; // offsets shifted by 0x94 from here on thanks to added 14 gamestats + /*0x1A08*/ struct SecretBaseRecord secretBases[20]; + /*0x2688*/ u8 playerRoomDecor[12]; + /*0x2694*/ u8 playerRoomDecorPos[12]; + /*0x26A0*/ u8 decorDesk[10]; + /*0x26AA*/ u8 decorChair[10]; + /*0x26B4*/ u8 decorPlant[10]; + /*0x26BE*/ u8 decorOrnament[30]; + /*0x26DC*/ u8 decorMat[30]; + /*0x26FA*/ u8 decorPoster[10]; + /*0x2704*/ u8 decorDoll[40]; + /*0x272C*/ u8 decorCushion[10]; + // /*0x2736*/ u8 padding_2736[2]; + /*0x2738*/ TVShow tvShows[24]; + /*0x2A98*/ u8 filler_2A98[0x64]; + /*0x2AFC*/ u16 outbreakPokemonSpecies; // offset by 0x94 + /*0x2AFE*/ u8 outbreakLocationMapNum; + /*0x2AFF*/ u8 outbreakLocationMapGroup; + /*0x2B00*/ u8 outbreakPokemonLevel; + /*0x2B01*/ u8 outbreakUnk1; + /*0x2B02*/ u16 outbreakUnk2; + /*0x2B04*/ u16 outbreakPokemonMoves[4]; + /*0x2B0C*/ u8 outbreakUnk4; + /*0x2B0D*/ u8 outbreakPokemonProbability; + /*0x2B0E*/ u16 outbreakUnk5; + /*0x2B10*/ u8 filler_2B0E[0xC]; + /*0x2B1C*/ u16 unk2B1C[4]; + /*0x2B24*/ u8 filler_2B24[0x28]; + /*0x2B4C*/ struct MailStruct mail[16]; // offset by 0x94 + /*0x2D8C*/ u8 filler_2D8C[0x8]; + /*0x2D94*/ OldMan oldMan; + /*0x2DC0*/ u8 unk_2DC0[0x14]; + /*0x2DD4*/ struct EasyChatPair easyChatPairs[5]; //Dewford trend [0] and some other stuff + // /*0x2DFC*/ u8 filler_2DFC[0x100]; + /*0x2EFC*/ struct SB1_2EFC_Struct sb1_2EFC_struct[12]; + u8 unk_3010[0x198]; // no idea if any of this is actually used. + /*0x3110*/ u8 giftRibbons[7]; + /*0x3117*/ u8 filler_311B[0x2D]; + /*0x3144*/ struct Roamer roamer; + /*0x3158*/ u8 filler_3158[0x8]; + /*0x3160*/ struct EnigmaBerryFRLGE enigmaBerry; + u8 unk_322C[0x1C0]; // 4 bytes of CRC16, then 444 bytes of unknown. Mystery Gift related. + u8 unk_33EC[0x150]; // 4 bytes of CRC16, then 332 bytes of unknown. Mystery Gift related. "mevent_buffer_1" + u8 unk_353C[0x150]; // 4 bytes of CRC16, then 332 bytes of unknown. Mystery Gift related. "mevent_buffer_2" + u8 unk_368C[0x9C]; // padding? doesn't seem to be actually used + /*0x3690*/ struct RamScript ramScript; + /*0x3A7C*/ u8 filler_3A7C[0x10]; + /*0x3A8C*/ u8 unk3A8C[52]; //pokedex related +} SaveBlock1_E; + +// --- + +struct SaveBlock2_Sub +{ + /*0x0000, 0x00A8*/ u8 filler_000[0x4AE]; + /*0x04AE, 0x0556*/ u8 var_4AE; + /*0x04AF, 0x0557*/ u8 var_4AF; + /*0x04B0, 0x0558*/ u16 var_4B0; + /*0x04B2, 0x055A*/ u16 var_4B2; + /*0x04B4, 0x055C*/ u16 var_4B4; + /*0x04B6, 0x055E*/ u16 var_4B6; + /*0x04B8, 0x0560*/ u8 filler_4B8[0x10]; + /*0x04C8, 0x0570*/ u16 var_4C8; + /*0x04CA, 0x0572*/ u16 var_4CA; + /*0x04CC, 0x0574*/ u8 filler_4CC[0x31C]; +}; + +typedef struct +{ + /*0x00*/ u8 playerName[8]; + /*0x08*/ u8 playerGender; // MALE, FEMALE + /*0x09*/ u8 specialSaveWarp; + /*0x0A*/ u8 playerTrainerId[4]; + /*0x0E*/ u16 playTimeHours; + /*0x10*/ u8 playTimeMinutes; + /*0x11*/ u8 playTimeSeconds; + /*0x12*/ u8 playTimeVBlanks; + /*0x13*/ u8 optionsButtonMode; // OPTIONS_BUTTON_MODE_[NORMAL/LR/L_EQUALS_A] + /*0x14*/ u16 optionsTextSpeed:3; // OPTIONS_TEXT_SPEED_[SLOW/MID/FAST] + u16 optionsWindowFrameType:5; // Specifies one of the 20 decorative borders for text boxes + u16 optionsSound:1; // OPTIONS_SOUND_[MONO/STEREO] + u16 optionsBattleStyle:1; // OPTIONS_BATTLE_STYLE_[SHIFT/SET] + u16 optionsBattleSceneOff:1; // whether battle animations are disabled + u16 regionMapZoom:1; // whether the map is zoomed in + /*0x18*/ struct Pokedex pokedex; + /*0x90*/ u8 filler_90[0x8]; + /*0x98*/ struct Time localTimeOffset; + /*0xA0*/ struct Time lastBerryTreeUpdate; + /*0xA8*/ struct SaveBlock2_Sub filler_A8; +} SaveBlock2_RS; + +typedef struct +{ + /*0x00*/ u8 playerName[8]; + /*0x08*/ u8 playerGender; // MALE, FEMALE + /*0x09*/ u8 specialSaveWarp; + /*0x0A*/ u8 playerTrainerId[4]; + /*0x0E*/ u16 playTimeHours; + /*0x10*/ u8 playTimeMinutes; + /*0x11*/ u8 playTimeSeconds; + /*0x12*/ u8 playTimeVBlanks; + /*0x13*/ u8 optionsButtonMode; // OPTIONS_BUTTON_MODE_[NORMAL/LR/L_EQUALS_A] + /*0x14*/ u16 optionsTextSpeed:3; // OPTIONS_TEXT_SPEED_[SLOW/MID/FAST] + u16 optionsWindowFrameType:5; // Specifies one of the 20 decorative borders for text boxes + u16 optionsSound:1; // OPTIONS_SOUND_[MONO/STEREO] + u16 optionsBattleStyle:1; // OPTIONS_BATTLE_STYLE_[SHIFT/SET] + u16 optionsBattleSceneOff:1; // whether battle animations are disabled + u16 regionMapZoom:1; // whether the map is zoomed in + /*0x18*/ struct Pokedex pokedex; + /*0x90*/ u8 filler_90[0x8]; + /*0x98*/ struct Time localTimeOffset; + /*0xA0*/ struct Time lastBerryTreeUpdate; + /*0xA8*/ struct SaveBlock2_Sub filler_A8; + /*0x890*/ u8 unk_890[8]; + /*0x898*/ u8 mapdata[0x258]; + /*0xaf0*/ u16 field_af0; + /*0xaf2*/ u16 field_af2; + /*0xaf4*/ u16 field_af4; + /*0xaf6*/ u16 field_af6; + /*0xaf8*/ u8 unk_af8[0x428]; + /*0xf20*/ u32 xor_key; +} SaveBlock2_FRLG; + +typedef struct +{ + /*0x00*/ u8 playerName[8]; + /*0x08*/ u8 playerGender; // MALE, FEMALE + /*0x09*/ u8 specialSaveWarp; + /*0x0A*/ u8 playerTrainerId[4]; + /*0x0E*/ u16 playTimeHours; + /*0x10*/ u8 playTimeMinutes; + /*0x11*/ u8 playTimeSeconds; + /*0x12*/ u8 playTimeVBlanks; + /*0x13*/ u8 optionsButtonMode; // OPTIONS_BUTTON_MODE_[NORMAL/LR/L_EQUALS_A] + /*0x14*/ u16 optionsTextSpeed:3; // OPTIONS_TEXT_SPEED_[SLOW/MID/FAST] + u16 optionsWindowFrameType:5; // Specifies one of the 20 decorative borders for text boxes + u16 optionsSound:1; // OPTIONS_SOUND_[MONO/STEREO] + u16 optionsBattleStyle:1; // OPTIONS_BATTLE_STYLE_[SHIFT/SET] + u16 optionsBattleSceneOff:1; // whether battle animations are disabled + u16 regionMapZoom:1; // whether the map is zoomed in + /*0x18*/ struct Pokedex pokedex; + /*0x90*/ u8 filler_90[0x8]; + /*0x98*/ struct Time localTimeOffset; + /*0xA0*/ struct Time lastBerryTreeUpdate; + /*0xA8*/ u32 xor_key; + /*0xAC*/ struct SaveBlock2_Sub filler_A8; +} SaveBlock2_E; + +typedef union { + SaveBlock1_RS rs; + SaveBlock1_FRLG frlg; + SaveBlock1_E e; +} SaveBlock1, *pSaveBlock1; + +typedef union { + SaveBlock2_RS rs; + SaveBlock2_FRLG frlg; + SaveBlock2_E e; +} SaveBlock2, *pSaveBlock2; + +typedef struct PokemonStorage SaveBlock3, *pSaveBlock3; \ No newline at end of file diff --git a/gba/source/savestructs.h b/gba/source/savestructs.h new file mode 100644 index 0000000..2bf4d4d --- /dev/null +++ b/gba/source/savestructs.h @@ -0,0 +1,793 @@ +/* + * Example Gen3-multiboot payload by slipstream/RoL 2017. + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + * + * saveblocks.h: describes structures used by saveblocks for all of Gen 3 + */ + +// Most of the structures come from pokeruby, FR/LG changes come from my own research / the firered IDB on pokecommunity + +#define POKEMON_NAME_LENGTH 10 +#define OT_NAME_LENGTH 7 +#define TILE_SIZE_4BPP 32 + +struct Coords16 +{ + s16 x; + s16 y; +}; + +struct UCoords16 +{ + u16 x; + u16 y; +}; + +struct SecretBaseRecord +{ + u8 sbr_field_0; // ID? + u8 sbr_field_1_0:4; + u8 gender:1; + u8 sbr_field_1_5:1; + u8 sbr_field_2[7]; // 0xFF bytes? + u8 trainerId[4]; // byte 0 is used for determining trainer class + u16 sbr_field_e; + u8 sbr_field_10; + u8 sbr_field_11; + u8 decorations[16]; + u8 sbr_field_22[16]; + u32 partyPersonality[6]; + u16 partyMoves[6 * 4]; + u16 partySpecies[6]; + u16 partyHeldItems[6]; + u8 partyLevels[6]; + u8 partyEVs[6]; +}; + +typedef void (*TilesetCB)(void); + +struct Tileset +{ + u8 isCompressed; + u8 isSecondary; + void *tiles; + void *palettes; + void *metatiles; + void *metatileAttributes; + TilesetCB callback; +}; + +struct MapData +{ + s32 width; + s32 height; + u16 *border; + u16 *map; + struct Tileset *primaryTileset; + struct Tileset *secondaryTileset; +}; + +struct MapObjectTemplate +{ + /*0x00*/ u8 localId; + /*0x01*/ u8 graphicsId; + /*0x02*/ u8 unk2; + /*0x04*/ s16 x; + /*0x06*/ s16 y; + /*0x08*/ u8 elevation; + /*0x09*/ u8 movementType; + /*0x0A*/ u8 unkA_0:4; + u8 unkA_4:4; + ///*0x0B*/ u8 fillerB[1]; + /*0x0C*/ u16 unkC; + /*0x0E*/ u16 unkE; + /*0x10*/ u8 *script; + /*0x14*/ u16 flagId; + /*0x16*/ u8 filler_16[2]; +}; /*size = 0x18*/ + +struct WarpEvent +{ + s16 x, y; + s8 warpId; + u8 mapGroup; + u8 mapNum; + u8 unk7; +}; + +struct CoordEvent +{ + s16 x, y; + u8 unk4; + u8 filler_5; + u16 trigger; + u16 index; + u8 filler_A[0x2]; + u8 *script; +}; + +struct BgEvent +{ + s16 x, y; + u8 unk4; + u8 kind; + s16 filler_6; + u8 *script; +}; + +struct MapEvents +{ + u8 mapObjectCount; + u8 warpCount; + u8 coordEventCount; + u8 bgEventCount; + + struct MapObjectTemplate *mapObjects; + struct WarpEvent *warps; + struct CoordEvent *coordEvents; + struct BgEvent *bgEvents; +}; + +struct MapConnection +{ + u8 direction; + u32 offset; + u8 mapGroup; + u8 mapNum; +}; + +struct MapConnections +{ + s32 count; + struct MapConnection *connections; +}; + +struct MapHeader +{ + struct MapData *mapData; + struct MapEvents *events; + u8 *mapScripts; + struct MapConnections *connections; + u16 music; + u16 mapDataId; + u8 name; + u8 cave; + u8 weather; + /* 0x17 */ u8 mapType; + u8 filler_18; + u8 escapeRope; + u8 flags; + u8 battleType; +}; + +struct MapObject +{ + /*0x00*/ u32 active:1; + u32 mapobj_bit_1:1; + u32 mapobj_bit_2:1; + u32 mapobj_bit_3:1; + u32 mapobj_bit_4:1; + u32 mapobj_bit_5:1; + u32 mapobj_bit_6:1; + u32 mapobj_bit_7:1; + /*0x01*/ u32 mapobj_bit_8:1; + u32 mapobj_bit_9:1; + u32 mapobj_bit_10:1; + u32 mapobj_bit_11:1; + u32 mapobj_bit_12:1; + u32 mapobj_bit_13:1; + u32 mapobj_bit_14:1; + u32 mapobj_bit_15:1; + /*0x02*/ u32 mapobj_bit_16:1; + u32 mapobj_bit_17:1; + u32 mapobj_bit_18:1; + u32 mapobj_bit_19:1; + u32 mapobj_bit_20:1; + u32 mapobj_bit_21:1; + u32 mapobj_bit_22:1; + u32 mapobj_bit_23:1; + /*0x03*/ u32 mapobj_bit_24:1; + u32 mapobj_bit_25:1; + u32 mapobj_bit_26:1; + u32 mapobj_bit_27:1; + u32 mapobj_bit_28:1; + u32 mapobj_bit_29:1; + u32 mapobj_bit_30:1; + u32 mapobj_bit_31:1; + /*0x04*/ u8 spriteId; + /*0x05*/ u8 graphicsId; + /*0x06*/ u8 animPattern; + /*0x07*/ u8 trainerType; + /*0x08*/ u8 localId; + /*0x09*/ u8 mapNum; + /*0x0A*/ u8 mapGroup; + /*0x0B*/ u8 mapobj_unk_0B_0:4; + u8 elevation:4; + /*0x0C*/ struct Coords16 coords1; + /*0x10*/ struct Coords16 coords2; + /*0x14*/ struct Coords16 coords3; + /*0x18*/ u8 mapobj_unk_18:4; //current direction? + /*0x18*/ u8 placeholder18:4; + /*0x19*/ u8 mapobj_unk_19; + /*0x1A*/ u8 mapobj_unk_1A; + /*0x1B*/ u8 mapobj_unk_1B; + /*0x1C*/ u8 mapobj_unk_1C; + /*0x1D*/ u8 trainerRange_berryTreeId; + /*0x1E*/ u8 mapobj_unk_1E; + /*0x1F*/ u8 mapobj_unk_1F; + /*0x20*/ u8 mapobj_unk_20; + /*0x21*/ u8 mapobj_unk_21; + /*0x22*/ u8 animId; + /*size = 0x24*/ +}; + +struct Berry +{ + const u8 name[7]; + u8 firmness; + u16 size; + u8 maxYield; + u8 minYield; + const u8 *description1; + const u8 *description2; + u8 stageDuration; + u8 spicy; + u8 dry; + u8 sweet; + u8 bitter; + u8 sour; + u8 smoothness; +}; + +struct EnigmaBerry +{ + struct Berry berry; + u8 pic[(6 * 6) * TILE_SIZE_4BPP]; + u16 palette[16]; + u8 description1[45]; + u8 description2[45]; + u8 itemEffect[18]; + u8 holdEffect; + u8 holdEffectParam; + u32 checksum; +}; + +struct BattleEnigmaBerry +{ + u8 name[7]; + u8 holdEffect; + u8 itemEffect[18]; + u8 holdEffectParam; +}; + +struct EnigmaBerryFRLGE { + struct Berry berry; // 0x00 + u8 itemEffect[18]; // 0x1C + u8 holdEffect; // 0x2E + u8 holdEffectParam; // 0x2F + u32 checksum; // 0x30 +}; + +struct __attribute__((aligned(4))) BerryTree +{ + u8 berry; + u8 stage:7; + u8 growthSparkle:1; + u16 secondsUntilNextStage; + u8 berryYield; + u8 regrowthCount:4; + u8 watered1:1; + u8 watered2:1; + u8 watered3:1; + u8 watered4:1; +}; + +struct PokemonSubstruct0 +{ + u16 species; + u16 heldItem; + u32 experience; + u8 ppBonuses; + u8 friendship; +}; + +struct PokemonSubstruct1 +{ + u16 moves[4]; + u8 pp[4]; +}; + +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 altAbility:1; + + /* 0x08 */ u32 coolRibbon:3; + /* 0x08 */ u32 beautyRibbon:3; + /* 0x08 */ u32 cuteRibbon:3; + /* 0x09 */ u32 smartRibbon:3; + /* 0x09 */ u32 toughRibbon:3; + /* 0x09 */ u32 championRibbon:1; + /* 0x0A */ u32 winningRibbon:1; + /* 0x0A */ u32 victoryRibbon:1; + /* 0x0A */ u32 artistRibbon:1; + /* 0x0A */ u32 effortRibbon:1; + /* 0x0A */ u32 giftRibbon1:1; + /* 0x0A */ u32 giftRibbon2:1; + /* 0x0A */ u32 giftRibbon3:1; + /* 0x0A */ u32 giftRibbon4:1; + /* 0x0B */ u32 giftRibbon5:1; + /* 0x0B */ u32 giftRibbon6:1; + /* 0x0B */ u32 giftRibbon7:1; + /* 0x0B */ u32 fatefulEncounter:5; // unused in Ruby/Sapphire, but the high bit must be set for Mew/Deoxys to obey in FR/LG/Emerald +}; + +union PokemonSubstruct +{ + struct PokemonSubstruct0 type0; + struct PokemonSubstruct1 type1; + struct PokemonSubstruct2 type2; + struct PokemonSubstruct3 type3; + u16 raw[6]; +}; + +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[OT_NAME_LENGTH]; + u8 markings; + u16 checksum; + u16 unknown; + + union + { + u32 raw[12]; + union PokemonSubstruct substructs[4]; + } secure; +}; + +struct Pokemon +{ + struct BoxPokemon box; + u32 status; + u8 level; + u8 pokerus; + u16 hp; + u16 maxHP; + u16 attack; + u16 defense; + u16 speed; + u16 spAttack; + u16 spDefense; +}; + +struct UnknownPokemonStruct +{ + u16 species; + u16 heldItem; + u16 moves[4]; + u8 level; + u8 ppBonuses; + u8 hpEV; + u8 attackEV; + u8 defenseEV; + u8 speedEV; + u8 spAttackEV; + u8 spDefenseEV; + u32 otId; + u32 hpIV:5; + u32 attackIV:5; + u32 defenseIV:5; + u32 speedIV:5; + u32 spAttackIV:5; + u32 spDefenseIV:5; + u32 gap:1; + u32 altAbility:1; + u32 personality; + u8 nickname[POKEMON_NAME_LENGTH + 1]; + u8 friendship; +}; + +struct BattlePokemon +{ + /* 0x00 */ u16 species; + /* 0x02 */ u16 attack; + /* 0x04 */ u16 defense; + /* 0x06 */ u16 speed; + /* 0x08 */ u16 spAttack; + /* 0x0A */ u16 spDefense; + /* 0x0C */ u16 moves[4]; + /* 0x14 */ u32 hpIV:5; + /* 0x14 */ u32 attackIV:5; + /* 0x15 */ u32 defenseIV:5; + /* 0x15 */ u32 speedIV:5; + /* 0x16 */ u32 spAttackIV:5; + /* 0x17 */ u32 spDefenseIV:5; + /* 0x17 */ u32 isEgg:1; + /* 0x17 */ u32 altAbility:1; + /* 0x18 */ s8 statStages[8]; + /* 0x20 */ u8 ability; + /* 0x21 */ u8 type1; + /* 0x22 */ u8 type2; + /* 0x23 */ u8 unknown; + /* 0x24 */ u8 pp[4]; + /* 0x28 */ u16 hp; + /* 0x2A */ u8 level; + /* 0x2B */ u8 friendship; + /* 0x2C */ u16 maxHP; + /* 0x2E */ u16 item; + /* 0x30 */ u8 nickname[POKEMON_NAME_LENGTH + 1]; + /* 0x3B */ u8 ppBonuses; + /* 0x3C */ u8 otName[8]; + /* 0x44 */ u32 experience; + /* 0x48 */ u32 personality; + /* 0x4C */ u32 status1; + /* 0x50 */ u32 status2; + /* 0x54 */ u32 otId; +}; + +struct BaseStats +{ + /* 0x00 */ u8 baseHP; + /* 0x01 */ u8 baseAttack; + /* 0x02 */ u8 baseDefense; + /* 0x03 */ u8 baseSpeed; + /* 0x04 */ u8 baseSpAttack; + /* 0x05 */ u8 baseSpDefense; + /* 0x06 */ u8 type1; + /* 0x07 */ u8 type2; + /* 0x08 */ u8 catchRate; + /* 0x09 */ u8 expYield; + /* 0x0A */ u16 evYield_HP:2; + /* 0x0A */ u16 evYield_Attack:2; + /* 0x0A */ u16 evYield_Defense:2; + /* 0x0A */ u16 evYield_Speed:2; + /* 0x0B */ u16 evYield_SpAttack:2; + /* 0x0B */ u16 evYield_SpDefense:2; + /* 0x0C */ u16 item1; + /* 0x0E */ u16 item2; + /* 0x10 */ u8 genderRatio; + /* 0x11 */ u8 eggCycles; + /* 0x12 */ u8 friendship; + /* 0x13 */ u8 growthRate; + /* 0x14 */ u8 eggGroup1; + /* 0x15 */ u8 eggGroup2; + /* 0x16 */ u8 ability1; + /* 0x17 */ u8 ability2; + /* 0x18 */ u8 safariZoneFleeRate; + /* 0x19 */ u8 bodyColor; +}; + +struct BattleMove +{ + u8 effect; + u8 power; + u8 type; + u8 accuracy; + u8 pp; + u8 secondaryEffectChance; + u8 target; + u8 priority; + u32 flags; +}; + +struct PokemonStorage +{ + /* 0x00 */ u8 currentBox; + /* 0x01 */ struct BoxPokemon boxes[14][30]; + u8 boxNames[14][9]; + u8 boxBackground[14]; +}; + +struct WarpData +{ + s8 mapGroup; + s8 mapNum; + s8 warpId; + s16 x, y; +}; + +struct ItemSlot +{ + u16 itemId; + u16 quantity; +}; + +struct __attribute__((aligned(2))) Pokeblock +{ + u8 color; + u8 spicy; + u8 dry; + u8 sweet; + u8 bitter; + u8 sour; + u8 feel; +}; + +struct Roamer +{ + /*0x00*/ u32 ivs; + /*0x04*/ u32 personality; + /*0x08*/ u16 species; + /*0x0A*/ u16 hp; + /*0x0C*/ u8 level; + /*0x0D*/ u8 status; + /*0x0E*/ u8 cool; + /*0x0F*/ u8 beauty; + /*0x10*/ u8 cute; + /*0x11*/ u8 smart; + /*0x12*/ u8 tough; + /*0x13*/ u8 active; +}; + +struct RamScriptData +{ + u8 magic; + u8 mapGroup; + u8 mapNum; + u8 objectId; + u8 script[995]; +} __attribute__((aligned(1),packed)); + +struct RamScript +{ + u32 checksum; + struct RamScriptData data; +} __attribute__((aligned(1),packed)); + +struct SB1_2EFC_Struct +{ + u8 unknown[0x20]; +}; + +struct EasyChatPair +{ + u16 unk0_0:7; + u16 unk0_7:7; + u16 unk1_6:1; + u16 unk2; + u16 words[2]; +}; /*size = 0x8*/ + +struct TVShowCommon { + /*0x00*/ u8 var00; + /*0x01*/ u8 var01; +}; + +struct TVShowFanClubLetter { + /*0x00*/ u8 var00; + /*0x01*/ u8 var01; + /*0x02*/ u16 species; + u8 pad04[12]; + /*0x10*/ u8 playerName[8]; + /*0x18*/ u8 var18; +}; + +struct TVShowRecentHappenings { + /*0x00*/ u8 var00; + /*0x01*/ u8 var01; + /*0x02*/ u16 var02; + u8 pad04[12]; + /*0x10*/ u8 var10[8]; + /*0x18*/ u8 var18; + u8 pad19[10]; +}; + +struct TVShowFanclubOpinions { + /*0x00*/ u8 var00; + /*0x01*/ u8 var01; + /*0x02*/ u16 var02; + /*0x04*/ u8 var04A:4; + u8 var04B:4; + /*0x04*/ u8 var05[8]; + /*0x0D*/ u8 var0D; + /*0x0E*/ u8 var0E; + /*0x0F*/ u8 var0F; + /*0x10*/ u8 var10[8]; +}; + +struct TVShowNameRaterShow { + /*0x00*/ u8 var00; + /*0x01*/ u8 var01; + /*0x02*/ u16 species; + /*0x04*/ u8 pokemonName[11]; + /*0x0F*/ u8 trainerName[11]; + /*0x1A*/ u8 random; + /*0x1B*/ u8 random2; + /*0x1C*/ u16 var1C; + /*0x1E*/ u8 language; + /*0x1F*/ u8 var1F; +}; + +struct TVShowMassOutbreak { + /*0x00*/ u8 var00; + /*0x01*/ u8 var01; + /*0x02*/ u8 var02; + /*0x03*/ u8 var03; + /*0x04*/ u16 moves[4]; + /*0x0C*/ u16 species; + /*0x0E*/ u16 var0E; + /*0x10*/ u8 locationMapNum; + /*0x11*/ u8 locationMapGroup; + /*0x12*/ u8 var12; + /*0x13*/ u8 probability; + /*0x14*/ u8 level; + /*0x15*/ u8 var15; + /*0x16*/ u16 var16; + /*0x18*/ u8 var18; + u8 pad19[11]; +}; + +typedef union TVShow { + struct TVShowCommon common; + struct TVShowFanClubLetter fanclubLetter; + struct TVShowRecentHappenings recentHappenings; + struct TVShowFanclubOpinions fanclubOpinions; + struct TVShowNameRaterShow nameRaterShow; + struct TVShowMassOutbreak massOutbreak; +} TVShow; + +struct __attribute__((aligned(4))) MailStruct +{ + /*0x00*/ u16 words[9]; + /*0x12*/ u8 playerName[8]; + /*0x1A*/ u8 trainerId[4]; + /*0x1E*/ u16 species; + /*0x20*/ u16 itemId; +}; + +struct UnkMauvilleOldManStruct +{ + u8 unk_2D94; + u8 unk_2D95; + /*0x2D96*/ u16 mauvilleOldMan_ecArray[6]; + /*0x2DA2*/ u16 mauvilleOldMan_ecArray2[6]; + /*0x2DAE*/ u8 playerName[8]; + /*0x2DB6*/ u8 filler_2DB6[0x3]; + /*0x2DB9*/ u8 playerTrainerId[4]; + u8 unk_2DBD; + /* size = 0x2C */ +}; + +struct UnkMauvilleOldManStruct2 +{ + u8 filler0; + u8 unk1; + u8 unk2; + u16 mauvilleOldMan_ecArray[10]; + u16 mauvilleOldMan_ecArray2[6]; + u8 fillerF[0x2]; + /* size = 0x2C */ +}; + +typedef union OldMan { + struct UnkMauvilleOldManStruct oldMan1; + struct UnkMauvilleOldManStruct2 oldMan2; +} OldMan; + +struct QuestStoryNPC { + u16 bitfield; + u8 direction; + u8 height; + u8 type_id; + u8 running_behaviour_or_picture_id; + u8 is_trainer; + u8 local_id; + u8 local_mapnumber; + u8 local_mapbank; + u16 x; + u16 y; + u8 sight_distance; + u8 role_from; + u8 unknown_decrement_on_step; + u8 unk_11; + u16 padding_12; +}; + +struct QuestStory { + u8 active; + u8 bank; + u8 map; + u8 warpId; + u16 x; + u16 y; + struct QuestStoryNPC npc[0x10]; + u8 unk_148[0x51f]; +}; + +struct NPCState { + u8 bitfield; + u8 obj_anim_and_vis_control; + u8 unk_2; + u8 unk_3; + u8 oamid; + u8 type_id; + u8 running_behaviour_or_picture_id; + u8 is_trainer; + u8 local_id; + u8 local_mapnumber; + u8 local_mapbank; + u8 height; + struct Coords16 stay_around; + struct Coords16 to; + struct Coords16 from; + u8 direction; + u8 movement_area; + u8 objid_surfing; + u8 objid_1B; + u8 idx_movement_behaviour; + u8 sight_distance; + u8 role_to; + u8 role_from; + u8 unk_20; + u8 unknown_decrement_on_step; + u8 unk_22; + u8 unk_23; +}; + +struct DaycarePokemon { + struct BoxPokemon pokemon; + u8 unk_50[56]; + u32 steps; +}; + + +struct Time +{ + /*0x00*/ s16 days; + /*0x02*/ s8 hours; + /*0x03*/ s8 minutes; + /*0x04*/ s8 seconds; +}; + +struct Pokedex +{ + /*0x00*/ u8 order; + /*0x01*/ u8 unknown1; + /*0x02*/ u8 nationalMagic; // must equal 0xDA in order to have National mode + /*0x03*/ u8 unknown2; + /*0x04*/ u32 unownPersonality; // set when you first see Unown + /*0x08*/ u32 spindaPersonality; // set when you first see Spinda + /*0x0C*/ u32 unknown3; + /*0x10*/ u8 owned[52]; + /*0x44*/ u8 seen[52]; +}; \ No newline at end of file -- cgit 1.4.1 From d642a84189db1ec39514bc1731b2b1ac5c704547 Mon Sep 17 00:00:00 2001 From: slipstream/RoL Date: Sun, 26 Feb 2017 14:33:30 +0000 Subject: Fix readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index afba524..3f84f0f 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ A GC and Wii homebrew app that sends a binary to the GBA using the different multiboot protocol used by the third generation of Pokémon games (Ruby, Sapphire, Emerald, FireRed, LeafGreen). # Usage -Have a GC Controller in Port 1 and a GBA with Gen3 game in Port 2. -Put your payload code to do some interesting stuff with the Pokémon game you have in `gba` dir. Example code that warps the character to the Hall of Fame, adding a Bad Egg if the save has no Pokémon provided. -Recompile, send to Wii or GC, turn on your GBA, hope that your code runs after the initial copyright screen of the game. +Have a GC Controller in Port 1 and a GBA with Gen3 game in Port 2. +Put your payload code to do some interesting stuff with the Pokémon game you have in `gba` dir. Example code that warps the character to the Hall of Fame, adding a Bad Egg if the save has no Pokémon provided. +Recompile, send to Wii or GC, turn on your GBA, hope that your code runs after the initial copyright screen of the game. (Code execution rate is for some reason not 100% reliable, PR to fix would be greatly appreciated. Sometimes KeyC derivation fails, sometimes GBA ignores the sent multiboot image, could be due to failure of a few different sends) # Acknowledgements -Thanks to FIX94 for your multiboot game dumper, which the multiboot code is loosely based on (differences in crypto & protocol...) +Thanks to FIX94 for your multiboot game dumper, which the multiboot code is loosely based on (differences in crypto & protocol...) Without it, this would have taken longer to do than the 2 days or so that it took. \ No newline at end of file -- cgit 1.4.1