-
-
Save grantland/5e2756592a0432773bda to your computer and use it in GitHub Desktop.
GBA Sleep Hack
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <cstdio> | |
#include <cassert> | |
#include <stdint.h> | |
#include <vector> | |
#include <string> | |
#include <iostream> | |
#include <iomanip> | |
using namespace std; | |
typedef uint8_t u8; | |
typedef uint32_t u32; | |
typedef uint16_t u16; | |
const u8 patch[] = {0x01, 0x13, 0xA0, 0xE3, 0x4C, 0x00, 0x01, 0xE5, 0x34, 0x00, 0x8F, 0xE2, 0x48, 0x00, 0x01, 0xE5, 0xE0, 0x01, 0x9F, 0xE5, 0x60, 0x00, 0x01, 0xE5, 0xDC, 0x01, | |
0x9F, 0xE5, 0x5C, 0x00, 0x01, 0xE5, 0xD8, 0x01, 0x9F, 0xE5, 0x58, 0x00, 0x01, 0xE5, 0xD4, 0x01, 0x9F, 0xE5, 0x54, 0x00, 0x01, 0xE5, 0xD0, 0x01, 0x9F, 0xE5, | |
0x50, 0x00, 0x01, 0xE5, 0xCC, 0x01, 0x9F, 0xE5, 0x04, 0x00, 0x01, 0xE5, 0x1E, 0xFF, 0x2F, 0xE1, 0x30, 0x21, 0x90, 0xE5, 0xFF, 0x24, 0xC2, 0xE3, 0xFF, 0x28, | |
0xC2, 0xE3, 0xF7, 0x00, 0x52, 0xE3, 0x2E, 0x00, 0x00, 0x0A, 0xF3, 0x00, 0x52, 0xE3, 0x13, 0x00, 0x00, 0x0A, 0x01, 0x01, 0x12, 0xE3, 0x06, 0x00, 0x00, 0x1A, | |
0x43, 0x34, 0xA0, 0xE3, 0x03, 0x37, 0x83, 0xE3, 0x30, 0x31, 0x80, 0xE5, 0x01, 0x1A, 0x81, 0xE3, 0x02, 0x2C, 0x80, 0xE2, 0xB0, 0x10, 0xC2, 0xE1, 0x4C, 0xF0, | |
0x10, 0xE5, 0xFF, 0x3C, 0xC2, 0xE3, 0xFF, 0x30, 0xC3, 0xE3, 0x7C, 0xC1, 0x9F, 0xE5, 0x0C, 0x00, 0x53, 0xE1, 0x4C, 0xF0, 0x10, 0x15, 0x01, 0x02, 0x11, 0xE3, | |
0xF4, 0xFF, 0xFF, 0x0A, 0x01, 0x3A, 0xA0, 0xE3, 0x02, 0x2C, 0x80, 0xE2, 0xB2, 0x30, 0xC2, 0xE1, 0xF0, 0xFF, 0xFF, 0xEA, 0x20, 0x10, 0x8F, 0xE2, 0x58, 0x30, | |
0x8F, 0xE2, 0x02, 0x24, 0xA0, 0xE3, 0x04, 0x00, 0x91, 0xE4, 0x04, 0x00, 0x82, 0xE4, 0x03, 0x00, 0x51, 0xE1, 0xFB, 0xFF, 0xFF, 0xBA, 0x02, 0x04, 0xA0, 0xE3, | |
0x01, 0x00, 0x80, 0xE2, 0x10, 0xFF, 0x2F, 0xE1, 0x20, 0x20, 0x83, 0x05, 0x00, 0x03, 0x1C, 0x18, 0x25, 0x18, 0x01, 0x02, 0x5A, 0x18, 0x19, 0x09, 0x56, 0x1A, | |
0x09, 0x09, 0x76, 0x18, 0x12, 0x1A, 0x17, 0x1A, 0xD2, 0x20, 0x00, 0x02, 0x15, 0x21, 0x09, 0x02, 0x10, 0x80, 0x19, 0x80, 0x20, 0x80, 0x29, 0x80, 0x18, 0x0B, | |
0x30, 0x80, 0x39, 0x80, 0xC1, 0x02, 0x08, 0x39, 0xFC, 0x20, 0x08, 0x60, 0x01, 0xDF, 0x00, 0xDF, 0xF0, 0x4F, 0x2D, 0xE9, 0x60, 0x10, 0x80, 0xE2, 0xFC, 0x03, | |
0xB1, 0xE8, 0xFC, 0x03, 0x2D, 0xE9, 0xFC, 0x03, 0xB1, 0xE8, 0xFC, 0x03, 0x2D, 0xE9, 0x02, 0x1C, 0x80, 0xE2, 0xB0, 0x40, 0xD1, 0xE1, 0x30, 0x51, 0x90, 0xE5, | |
0xB0, 0x60, 0xD0, 0xE1, 0xD0, 0x10, 0x9F, 0xE5, 0x00, 0x12, 0x80, 0xE5, 0x03, 0x11, 0xA0, 0xE3, 0x03, 0x17, 0x81, 0xE3, 0x30, 0x11, 0x80, 0xE5, 0xB4, 0x08, | |
0xC0, 0xE1, 0x80, 0x10, 0x86, 0xE3, 0xB0, 0x10, 0xC0, 0xE1, 0x00, 0x00, 0x03, 0xEF, 0x01, 0x03, 0xA0, 0xE3, 0x30, 0x11, 0x90, 0xE5, 0x0C, 0x10, 0x01, 0xE2, | |
0x0C, 0x00, 0x51, 0xE3, 0xFA, 0xFF, 0xFF, 0x1A, 0xB6, 0x10, 0xD0, 0xE1, 0x9F, 0x00, 0x51, 0xE3, 0xFC, 0xFF, 0xFF, 0x1A, 0xB6, 0x10, 0xD0, 0xE1, 0xA0, 0x00, | |
0x51, 0xE3, 0xFC, 0xFF, 0xFF, 0x1A, 0xB6, 0x10, 0xD0, 0xE1, 0x9F, 0x00, 0x51, 0xE3, 0xFC, 0xFF, 0xFF, 0x1A, 0xB6, 0x10, 0xD0, 0xE1, 0xA0, 0x00, 0x51, 0xE3, | |
0xFC, 0xFF, 0xFF, 0x1A, 0xB6, 0x10, 0xD0, 0xE1, 0x9F, 0x00, 0x51, 0xE3, 0xFC, 0xFF, 0xFF, 0x1A, 0x02, 0x1C, 0x80, 0xE2, 0xB0, 0x40, 0xC1, 0xE1, 0x30, 0x51, | |
0x80, 0xE5, 0x01, 0x4A, 0xA0, 0xE3, 0xB2, 0x40, 0xC1, 0xE1, 0xB0, 0x60, 0xC0, 0xE1, 0xFC, 0x03, 0xBD, 0xE8, 0x84, 0x30, 0x80, 0xE5, 0x80, 0x10, 0x80, 0xE2, | |
0xFC, 0x03, 0xA1, 0xE8, 0x60, 0x10, 0x80, 0xE2, 0xFC, 0x03, 0xBD, 0xE8, 0xFC, 0x03, 0xA1, 0xE8, 0xF0, 0x4F, 0xBD, 0xE8, 0xB6, 0x10, 0xD0, 0xE1, 0xA0, 0x00, | |
0x51, 0xE3, 0xFC, 0xFF, 0xFF, 0x1A, 0x4C, 0xF0, 0x10, 0xE5, 0x00, 0x12, 0x90, 0xE5, 0x01, 0x08, 0x11, 0xE3, 0x01, 0x02, 0x11, 0x03, 0x4C, 0xF0, 0x10, 0x05, | |
0x48, 0xF0, 0x10, 0xE5, 0xA0, 0x7F, 0x00, 0x03, 0x00, 0x00, 0x0C, 0x43, 0x00, 0x10, 0xFF, 0xFF}; | |
template <class D> | |
bool readfile(D &data, const std::string& filename) | |
{ | |
FILE *f; | |
f=::fopen(filename.c_str(),"rb"); | |
if (!f) | |
{ | |
return false; | |
} | |
size_t filesize; | |
::fseek(f,0,SEEK_END); | |
filesize=::ftell(f); | |
::fseek(f,0,SEEK_SET); | |
size_t elt_size=sizeof(data[0]); | |
size_t num_elts=(filesize+(elt_size-1))/elt_size; | |
data.resize(num_elts); | |
size_t amount_read; | |
amount_read=::fread(&data[0],elt_size,num_elts,f); | |
::fclose(f); | |
if (amount_read!=filesize) | |
{ | |
return false; | |
} | |
return true; | |
} | |
template <class D> | |
bool writefile(const D &data, const std::string& filename) | |
{ | |
FILE *f; | |
f=::fopen(filename.c_str(),"wb"); | |
if (!f) | |
{ | |
return false; | |
} | |
size_t filesize; | |
filesize=data.size(); | |
size_t elt_size=sizeof(data[0]); | |
size_t num_elts=(filesize+(elt_size-1))/elt_size; | |
size_t amount_written; | |
amount_written=::fwrite(&data[0],elt_size,num_elts,f); | |
::fclose(f); | |
if (amount_written!=filesize) | |
{ | |
return false; | |
} | |
return true; | |
} | |
void hack_it(const vector<u8> &infile, vector<u8> &outfile); | |
void build_activation(u32* mem_base, u32 activation_address, u32 routine_address, u32 return_address, u32 rA, u32 rB); | |
int main(int argc, char **argv, char**envp) | |
{ | |
vector<u8> infile; | |
vector<u8> outfile; | |
vector<u8> patch; | |
cerr << "Applies Sleep Mode patch to GBA game!"<<endl<< | |
"(Press L+R+START to sleep, SEL+START to wake up)"<<endl; | |
if (argc!=3) | |
{ | |
cerr << "syntax: <infile> <outfile>"<<endl; | |
return 1; | |
} | |
string infilename=argv[1]; | |
string outfilename=argv[2]; | |
if (!readfile(infile,infilename)) | |
{ | |
cerr << "Can't read " <<infilename<< endl; | |
return 2; | |
} | |
hack_it(infile,outfile); | |
if (!writefile(outfile,outfilename)) | |
{ | |
cerr << "Can't write "<<outfilename<<endl; | |
return 3; | |
} | |
return 0; | |
} | |
const int ACTIVATION_SIZE=36; | |
void hack_it(const vector<u8> &infile, vector<u8> &outfile) | |
{ | |
const u32 PATCH_SIZE = sizeof(patch)/sizeof(patch[0]); | |
//The LDR,=xxxxxxxx ARM instruction: | |
// | |
//Load/Store=1 | |
//Writeback=0 | |
//Byte/Word=0 | |
//Up/Down=either | |
//Pre/Post=? | |
//Immediate Offset=0 | |
//rn = r15 | |
//cond= always | |
//1110 01 0 p u 0 0 1 1111 rrrr oooooooooooo | |
//Instruction Mask: | |
//1111 11 1 0 0 1 1 1 1111 0000 000000000000 | |
//Instruction Bits: | |
//1110 01 0 0 0 0 0 1 1111 0000 000000000000 | |
//The STR rd,[rn] ARM instruction: | |
//Load/Store=0 | |
//Writeback=0 | |
//Byte/Word=0 | |
//Up/Down=? | |
//Pre/Post=? | |
//Immediate Offset=0 | |
//cond= always | |
//1110 01 0 p u 0 0 0 rnrn rdrd 000000000000 | |
//MASK: | |
//1111 11 1 0 0 1 1 1 0000 0000 111111111111 | |
//BITS: | |
//1110 01 0 0 0 0 0 0 0000 0000 000000000000 | |
//THUMB: | |
//LDR rd,=xxxxxxxx | |
//01001rrroooooooo | |
//1111100000000000 | |
//0100100000000000 | |
//STR rd,[rb] | |
//01100 00000 bbb ddd | |
//11111 11111 000 000 | |
//01100 00000 000 000 | |
const u32 ldr_mask = 0xFE7F0000; | |
const u32 ldr_bits = 0xE41F0000; | |
const u32 str_mask = 0xFE700FFF; | |
const u32 str_bits = 0xE4000000; | |
const u32 t_ldr_mask = 0xF800; | |
const u32 t_ldr_bits = 0x4800; | |
const u32 t_str_mask = 0xFFC0; | |
const u32 t_str_bits = 0x6000; | |
const u32 UP_DOWN_BIT_MASK = 0x800000; | |
const u32 REG_DEST_SHIFT = 12; | |
const u32 REG_SRC_SHIFT = 16; | |
const u32 REG_MASK = 0x0F; | |
const u32 OFFSET_MASK = 0xFFF; | |
u32 reg_data[16]; //contents of register | |
u32 reg_addr[16]; //address of the data which got written | |
u32 reg_lastwrite[16]; //PC at which the data was written to | |
memset(®_data[0],0,16*sizeof(u32)); | |
memset(®_addr[0],0,16*sizeof(u32)); | |
memset(®_lastwrite[0],0,16*sizeof(u32)); | |
int trim_size; | |
u32 patch_location; | |
u32 activation_location; | |
/* First Trim the rom... */ | |
{ | |
int i; | |
int l=infile.size(); | |
u8 b; | |
b=infile[infile.size()-1]; | |
for (i=infile.size()-1;i>=0;i--) | |
{ | |
if (infile[i]!=b) | |
{ | |
break; | |
} | |
} | |
if (i>0) | |
{ | |
l=i+4; | |
l=l+(16-(l&15)); | |
if (l>infile.size()) l=infile.size(); | |
} | |
trim_size=l; | |
} | |
patch_location=trim_size; | |
activation_location=patch_location + PATCH_SIZE; | |
outfile.resize(trim_size); | |
memcpy(&outfile[0],&infile[0],trim_size); | |
outfile.resize(outfile.size() + PATCH_SIZE); | |
// copy patch at the end of the trimmed rom | |
memcpy(&outfile[patch_location],&patch[0], PATCH_SIZE); | |
assert(outfile.size()==activation_location); | |
const u32 *base; | |
u32 size_in_words = infile.size() / sizeof(u32); | |
base = (u32*)(&infile[0]); | |
//this code is Little Endian Only | |
u32 wordnumber=0; | |
for (wordnumber = 0; wordnumber < size_in_words; wordnumber++) | |
{ | |
u32 word=base[wordnumber]; | |
//check for LDR xx,=xxxxxxxx | |
if ((word&ldr_mask)==ldr_bits) | |
{ | |
// real PC, takes care of ARM7 pipeline so adds 8 | |
u32 pc = wordnumber * sizeof(u32) + 8; | |
//1110 01 0 p u 0 0 1 1111 rrrr oooooooooooo | |
//0000 00 0 0 1 0 0 0 0000 0000 000000000000 | |
// if u bit is set we're adding to base offset, otherwise subtracting | |
int updown = word & UP_DOWN_BIT_MASK ? 1 : -1; | |
// destination register | |
int rd = (word>> REG_DEST_SHIFT) & REG_MASK; | |
// offset from base | |
int offset = word& OFFSET_MASK; | |
// compute address of load operation | |
u32 address = (updown)*offset + pc; | |
u32 memdata=0xDEADBEEF; | |
// if we're accessing an address within the bounds of the rom | |
if (address < infile.size() - 3) | |
{ | |
// if the address is four bytes aligned ((address & 0x03) == 0) | |
if (address % 4 == 0) | |
{ | |
memdata = base[address/sizeof(u32)]; | |
} | |
} | |
// what it is going to be loaded in rd by LDR | |
reg_data[rd] = memdata; | |
// where it is going to be read from LDR | |
reg_addr[rd] = address; | |
// positition of the LDR instruction | |
reg_lastwrite[rd] = wordnumber*sizeof(u32); | |
} | |
else if ((word & str_mask) == str_bits) | |
{ | |
//1110 01 0 p u 0 0 0 rnrn rdrd 000000000000 | |
int rn,rd; | |
// base register | |
rn = (word >> REG_SRC_SHIFT) & REG_MASK; | |
// source register | |
rd = (word >> REG_DEST_SHIFT) & REG_MASK; | |
// current instruction position | |
u32 myaddress=wordnumber*sizeof(u32); | |
bool okay=true | |
&& (reg_data[rn]==0x03007FFC) // base register is the interrupt address | |
&& (myaddress - reg_lastwrite[rd] < 64) // last instruction that wrote on source reg is near | |
&& (myaddress - reg_lastwrite[rn] < 64) // last instruction that wrote on base reg is near | |
&& (reg_lastwrite[rd] != 0) // we already found a ldr to source reg | |
&& (reg_lastwrite[rn] != 0) // we already found a ldr to base reg | |
&& ((reg_data[rd] & 0xFF000000) == 0x03000000) // source register contains an address value which is in IWRAM | |
; | |
// if STR has been found directly in joybus entry point? | |
if (!okay && reg_data[rn]==0x03007FFC && myaddress == 0xE0) | |
okay=true; | |
// if the STR instruction comes before last write to base or source reg, | |
// how this can happen if file is scanned sequentially? | |
if (myaddress<reg_lastwrite[rd] || myaddress<reg_lastwrite[rn]) | |
okay=false; | |
if (okay) | |
{ | |
{ | |
u32 *out32=(u32*)&outfile[myaddress]; | |
// replace str rd,[rn] >> BX rn | |
out32[0] = 0xE12FFF10 + rn; | |
// then go back to the LDR instruction that setup this interrupt installation routine | |
// replace ldr rn,=XXXX >> ldr rn,=activation_jump by changing the value loaded | |
u32 data_address = reg_addr[rn]; | |
u32 *outbase32 = (u32*)&outfile[0]; | |
if (data_address<infile.size()-3) | |
outbase32[data_address/sizeof(u32)] = 0x08000000 + activation_location; | |
} | |
// append space for activation routine | |
outfile.resize(outfile.size()+ACTIVATION_SIZE); | |
// install activation routine | |
build_activation((u32*)&outfile[0], 0x08000000 + activation_location, 0x08000000 + patch_location, 0x08000000 + myaddress + 4,rd,rn); | |
// move next location for next activation | |
activation_location += ACTIVATION_SIZE; | |
cout << "Patched an interrupt installer!" << endl; | |
cout << hex << setfill('0') << "@0x"<< setw(8) << reg_lastwrite[rd] <<": r"<<rd<<"=0x"<< reg_data[rd] << endl; | |
cout << "@0x" << setw(8) << reg_lastwrite[rn]<<": r"<<rn<<"=0x"<< reg_data[rn] <<endl; | |
cout << "@0x"<< setw(8) << myaddress <<": str r"<<rd<<",[r"<<rn<<"]"<<endl; | |
// reg_data[rn]=0; | |
} | |
} | |
} | |
//YAY now for THUMB version! | |
memset(®_data[0],0,16*sizeof(u32)); | |
memset(®_addr[0],0,16*sizeof(u32)); | |
memset(®_lastwrite[0],0,16*sizeof(u32)); | |
const u16 *base16; | |
int size_in_halfwords=infile.size()/sizeof(u16); | |
base16=(u16*)(&infile[0]); | |
//this code is Little Endian Only | |
for (wordnumber=0;wordnumber<size_in_halfwords;wordnumber++) | |
{ | |
u16 word=base16[wordnumber]; | |
//check for LDR xx,=xxxxxxxx | |
if ((word&t_ldr_mask)==t_ldr_bits) | |
{ | |
u32 pc=wordnumber*sizeof(u16)+4; | |
//01001rrroooooooo | |
int rd=((word>>8)&0x07); | |
int offset = (word&0xFF); | |
u32 address = offset*4 + pc; | |
u32 memdata=0xDEADBEEF; | |
address&=~0x03; | |
if (address<infile.size()-3) | |
{ | |
if ((address&0x03)==0x00) | |
{ | |
memdata=base[address/sizeof(u32)]; | |
} | |
} | |
reg_data[rd]=memdata; | |
reg_addr[rd]=address; | |
reg_lastwrite[rd]=wordnumber*sizeof(u16); | |
} | |
else if ((word&t_str_mask)==t_str_bits) | |
{ | |
//01100 00000 bbb ddd | |
int rb,rd; | |
rb=((word>>3)&0x07); | |
rd=((word>>0)&0x07); | |
u32 myaddress=wordnumber*sizeof(u16); | |
bool okay=true | |
&& (reg_data[rb]==0x03007FFC) | |
&& (myaddress-reg_lastwrite[rd]<64) | |
&& (myaddress-reg_lastwrite[rb]<64) | |
&& (reg_lastwrite[rd]!=0) | |
&& (reg_lastwrite[rb]!=0) | |
&& ((reg_data[rd]&0xFF000000)==0x03000000) | |
; | |
if (okay) | |
{ | |
{ | |
u16 *out16=(u16*)&outfile[myaddress]; | |
//str rd,[rn] >> BX rb | |
out16[0]=0x4700+(rb<<3); | |
//ldr rn,=XXXX >> ldr rn,=activation_jump | |
u32 *outbase32=(u32*)&outfile[0]; | |
u32 data_address=reg_addr[rb]; | |
if (data_address<infile.size()-3) | |
{ | |
outbase32[data_address/sizeof(u32)]=0x08000000+activation_location; | |
} | |
} | |
outfile.resize(outfile.size()+ACTIVATION_SIZE); | |
build_activation((u32*)&outfile[0],0x08000000+activation_location,0x08000000+patch_location,0x08000000+myaddress+1+2,rd,rb); | |
activation_location+=ACTIVATION_SIZE; | |
cout << "Patched a THUMB interrupt installer!" << endl; | |
cout << hex << setfill('0') << "@0x" << setw(8) << reg_lastwrite[rd] << ": r" << rd << "=0x" << reg_data[rd] << endl; | |
cout << "@0x"<< setw(8) << reg_lastwrite[rb] <<": r"<<rb<<"=0x"<< reg_data[rb] <<endl; | |
cout << "@0x"<< setw(8) << myaddress <<": str r"<<rd<<",[r"<<rb<<"]"<<endl; | |
// reg_data[rb]=0; | |
} | |
} | |
} | |
} | |
void build_activation(u32* mem_base, u32 activation_address, u32 routine_address, u32 return_address, u32 rA, u32 rB) | |
{ | |
//writes 36 bytes to *activation_base | |
/* | |
E92D5003 stmfd sp!,{r0,r1,r12,lr} | |
E1A0C000 *mov r12,rA | |
E1A01000 *mov r1,rB | |
E1A0000C mov r0,r12 | |
EBFFFFFA *bl call_address | |
E8BD5003 ldmfd sp!,{r0,r1,r12,lr} | |
E59F0000 *ldr rB,=return_adress | |
E12FFF10 *bx rB | |
DEADBEEF *Literal return_address | |
*/ | |
u32* mem=(u32*)(&(((u8*)mem_base)[activation_address-0x08000000])); | |
mem[0]=0xE92D5003; | |
mem[1]=0xE1A0C000 + rA; | |
mem[2]=0xE1A01000 + rB; | |
mem[3]=0xE1A0000C; | |
mem[4]=0xEB000000 + ((0x00FFFFFF)&((routine_address-(activation_address+8+16))>>2)); | |
mem[5]=0xE8BD5003; | |
mem[6]=0xE59F0000 + (rB<<12); | |
mem[7]=0xE12FFF10+rB; | |
mem[8]=return_address; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment