Skip to content

Instantly share code, notes, and snippets.

@grantland
Forked from Jakz/sleephack.cpp
Created October 2, 2015 07:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save grantland/5e2756592a0432773bda to your computer and use it in GitHub Desktop.
Save grantland/5e2756592a0432773bda to your computer and use it in GitHub Desktop.
GBA Sleep Hack
#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(&reg_data[0],0,16*sizeof(u32));
memset(&reg_addr[0],0,16*sizeof(u32));
memset(&reg_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(&reg_data[0],0,16*sizeof(u32));
memset(&reg_addr[0],0,16*sizeof(u32));
memset(&reg_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