Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The day I reverse engineered a furry Gameboy Ad...

The day I reverse engineered a furry Gameboy Advance game

🎥 Talk: https://www.youtube.com/watch?v=RMM_5bq3Ct8

Let’s learn how you can create your own ROM hacking for your favorite game!

You will discover how the loved Gameboy Advance works, understand a crazy ARM assembly and reverse engineering stuff, to create a level editor running on the browser.

Reverse engineering is a heavy topic, so what do you think of learning it in a fun way through this challenge: developing a level editor for the furry game “Klonoa: Empire of Dreams”?
It's an exciting challenge because we need to understand the GBA's architecture, discover how the game works, and finally use our knowledge to build our tool.

Bruno Macabeus

August 14, 2021
Tweet

More Decks by Bruno Macabeus

Other Decks in Programming

Transcript

  1. This is the tile ID. In this example, all tiles

    with the ID 9B are the bridge left-corner. 9A is the ID of the continuous part of the bridge.
  2. This tells us where the tile is stored in the

    memory. In this example, it is at 0600F1AD.
  3. VRAM pulls the tilemap containing exactly what the player has

    to see from somewhere else that has the entire thing.
  4. https://www.coranac.com/tonc/text/regbg.htm It is a feature of computer systems that allows

    certain hardware subsystems to access main system memory (random access memory), independent of the CPU
  5. DMA3: 03000900 0600E000 80000400 DMA3: 03001100 0600E800 80000400 DMA3: 03004DB0

    0600F000 80000200 DMA3: 03004800 07000000 84000048 The closest to the bytes of our bridge (0600F1AD).
  6. DMA3: 03000900 0600E000 80000400 DMA3: 03001100 0600E800 80000400 DMA3: 03004DB0

    0600F000 80000200 DMA3: 03004800 07000000 84000048 In other words, VRAM is updated here
  7. DMA3: 03000900 0600E000 80000400 DMA3: 03001100 0600E800 80000400 DMA3: 03004DB0

    0600F000 80000200 DMA3: 03004800 07000000 84000048 This byte is the VRAM data source. It’s located in the Fast WRAM section.
  8. OAM

  9. DMA3: 03000900 0600E000 80000400 DMA3: 03001100 0600E800 80000400 DMA3: 03004DB0

    0600F000 80000200 DMA3: 03004800 07000000 8400003E We can see exactly the byte that writes on Klonoa’s object (07000000).
  10. DMA3: 03000900 0600E000 80000400 DMA3: 03001100 0600E800 80000400 DMA3: 03004DB0

    0600F000 80000200 DMA3: 03004800 07000000 8400003E We can see exactly the byte that writes on Klonoa’s object (07000000).
  11. …on running the next frame, we can notices that it

    changes the Klonoa Y position! But also returns this byte for the correct value…
  12. …and it uses the tile id stored at the Slow

    WRAM Here is where it decides if it should update or not the Klonoa's position…
  13. LET’S find the tilemap on rom THE DAY I REVERSE

    ENGINEERED A GAMEBOY ADVANCE GAME
  14. BIOS is a firmware that is intended to initialise the

    physical components of the system
  15. BIOS is a firmware that is intended to initialise the

    physical components of the system In GBA, its BIOS exposes many functions often used in games, including data compression and decompression
  16. BIOS is a firmware that is intended to initialise the

    physical components of the system In GBA, its BIOS exposes many functions often used in games, including data compression and decompression Each function has an associated numeric code, which must be used as a parameter in the SWI statement
  17. How division works on GBA: swi 0x06 Input: R0 


    numerator R1 
 denominator R0 
 numerator / denominator R1 
 numerator % denominator R3 
 abs(numerator / denominator) Output:
  18. And in this case, the software is calling the Huffman

    Decompress and lz77 Decompress functions
  19. 6E 6E 6E B0 B1 BD 77 AB B3 AB

    B3 7C 6E 6E 6E 6E 6E 6E 6E 6E 7C 6E 6E 6E 6E 7C 6E 6E
  20. 6E 6E 6E B0 B1 BD 77 AB B3 AB

    B3 7C 6E 6E 6E 6E 6E 6E 6E 6E 7C 6E 6E 6E 6E 7C 6E 6E 6E 6E 6E B0 B1 BD 77 AB B3 AB B3 7C 6E 6E 6E 6E 6E 6E 6E 6E 7C 6E 6E 6E 6E 7C 6E 6E
  21. 6E 6E 6E B0 B1 BD 77 AB B3 AB

    B3 7C 6E 6E 6E 6E 6E 6E 6E 6E 7C 6E 6E 6E 6E 7C 6E 6E 6E 6E 6E B0 B1 BD 77 AB B3 AB B3 7C 6E 6E 6E 6E 6E 6E 6E 6E 7C 6E 6E 6E 6E 7C 6E 6E =
  22. 0x1A 0x1B 0x1C 0x1D 0x1E 0x1F 0x1A 0x1B 0x1C 0x1D

    0x1E 0x1F 0x1A 0x1B 0x1C 0x1D 0x1E 0x1F 0x1A 0x1B 0x1C 0x1D 0x1E 0x1F
  23. 0x1A 0x1B 0x1C 0x1D 0x1E 0x1F 0x1A 0x1B 0x1C 0x1D

    0x1E 0x1F 0x1A 0x1B 0x1C 0x1D 0x1E 0x1F 0x1A 0x1B 0x1C 0x1D 0x1E 0x1F
  24. x x x x a x x x x 


    x x x x b x x x x I’m on tile a Below me is the tile b
  25. x x x x a x x x x 


    x x x x b x x x x From the tile a to b there are 9 bytes (= 9 tiles), so the length of the tilemap is exactly 9 I’m on tile a
  26. When I change this byte… …it reflects right there, and

    it’s our tile a,… …at 02008439
  27. const Jimp = require('jimp') const { drop } = require('ramda')

    const fs = require('fs') const data = drop(3, fs.readFileSync(‘dump/level-1/tilemap'))
  28. const Jimp = require('jimp') const { drop } = require('ramda')

    const fs = require('fs') const data = drop(3, fs.readFileSync('dump/level-1/tilemap'))
  29. const getPixelColor = hexTile = > { if (hexTile ===

    0) { return 0x000000 } return 0xFFFFFF }
  30. new Jimp(300, 600, (err, image) = > { let x

    = 0 let y = 0 for (let i = 0; i < data.length; i += 1) { const value = data[i] x += 1 if (x === 420) { y += 1 x = 0 } let color = getPixelColor(value) image.setPixelColor(color, x, y) } image.write('image.png') })
  31. Z 🖌 ✂ Extract data 
 from the ROM Get

    constants values Update the 
 ROM data UI
  32. const lzssModule = require('./wasm/lzss.js' ) const { FS } =

    lzssModul e const lzssDecode = (buffer) => { FS.writeFile('filelzss', buffer ) lzssModule._LZS_Decode( ) try { return lzssModule.FS.readFile('filelzss', { encoding: 'binary' } ) } catch (e) { throw new LzssDecodeError(e ) } }
  33. const lzssModule = require('./wasm/lzss.js' ) const { FS } =

    lzssModul e const lzssDecode = (buffer) => { FS.writeFile('filelzss', buffer ) lzssModule._LZS_Decode( ) try { return lzssModule.FS.readFile('filelzss', { encoding: 'binary' } ) } catch (e) { throw new LzssDecodeError(e ) } }
  34. const lzssModule = require('./wasm/lzss.js' ) const { FS } =

    lzssModul e const lzssDecode = (buffer) => { FS.writeFile('filelzss', buffer ) lzssModule._LZS_Decode( ) try { return lzssModule.FS.readFile('filelzss', { encoding: 'binary' } ) } catch (e) { throw new LzssDecodeError(e ) } }
  35. const lzssModule = require('./wasm/lzss.js' ) const { FS } =

    lzssModul e const lzssDecode = (buffer) => { FS.writeFile('filelzss', buffer ) lzssModule._LZS_Decode( ) try { return lzssModule.FS.readFile('filelzss', { encoding: 'binary' } ) } catch (e) { throw new LzssDecodeError(e ) } }
  36. const lzssModule = require('./wasm/lzss.js' ) const { FS } =

    lzssModul e const lzssDecode = (buffer) => { FS.writeFile('filelzss', buffer ) lzssModule._LZS_Decode( ) try { return lzssModule.FS.readFile('filelzss', { encoding: 'binary' } ) } catch (e) { throw new LzssDecodeError(e ) } }
  37. const lzssModule = require('./wasm/lzss.js' ) const { FS } =

    lzssModul e const lzssDecode = (buffer) => { FS.writeFile('filelzss', buffer ) lzssModule._LZS_Decode( ) try { return lzssModule.FS.readFile('filelzss', { encoding: 'binary' } ) } catch (e) { throw new LzssDecodeError(e ) } }
  38. function docker_run_emscripten { local filename="$1" echo "Compiling $filename..." docker run

    \ --rm -it \ -v $(pwd)/scissors/src/wasm:/src \ trzeci/emscripten:1.38.43 \ emcc -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s MODULARIZE=1 \ -s EXTRA_EXPORTED_RUNTIME_METHODS=[\”FS\"] \ -s EXPORT_NAME=\"$filename\" -o ./$filename.js $filename. c } docker_run_emscripten huffma n docker_run_emscripten lzss
  39. function docker_run_emscripten { local filename="$1 " echo "Compiling $filename... "

    docker run \ --rm -it \ -v $(pwd)/scissors/src/wasm:/src \ trzeci/emscripten:1.38.43 \ emcc -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s MODULARIZE=1 \ -s EXTRA_EXPORTED_RUNTIME_METHODS=[\”FS\"] \ -s EXPORT_NAME=\"$filename\" -o ./$filename.js $filename. c } docker_run_emscripten huffma n docker_run_emscripten lzss
  40. The switch between ARM and Thumb must be done by

    bx instruction. This instruction only can read a register
  41. The switch between ARM and Thumb must be done by

    bx instruction. This instruction only can read a register bx updates the PC by the register value used in its parameter
  42. Very big hole space at the end of cartridge First

    Level Second Level Third Level Instruction to load a level
  43. First Level Second Level Third Level Instruction to load a

    level Constant addresses map table Hole Customised level loader Customised First Level Customised Second Level Customised Third Level Hole Hole Hole
  44. First Level Second Level Third Level Instruction to load a

    level Constant addresses map table Hole Customised level loader Customised First Level Customised Second Level Customised Third Level Hole Hole Hole 0836720 FC 27 1B 0 8 0836724 00 77 36 0 8 0836728 5C 3E 1B 0 8 083672C B0 86 36 0 8 0836730 AC 50 1B 0 8 0836734 80 93 36 08
  45. First Level Second Level Third Level Instruction to load a

    level Constant addresses map table Hole Customised level loader Customised First Level Customised Second Level Customised Third Level Hole Hole Hole 0836720 FC 27 1B 0 8 0836724 00 77 36 0 8 0836728 5C 3E 1B 0 8 083672C B0 86 36 0 8 0836730 AC 50 1B 0 8 0836734 80 93 36 08
  46. First Level Second Level Third Level Instruction to load a

    level Constant addresses map table Hole Customised level loader Customised First Level Customised Second Level Customised Third Level Hole Hole Hole 0836720 FC 27 1B 0 8 0836724 00 77 36 0 8 0836728 5C 3E 1B 0 8 083672C B0 86 36 0 8 0836730 AC 50 1B 0 8 0836734 80 93 36 08
  47. First Level Second Level Third Level Instruction to load a

    level Constant addresses map table Hole Customised level loader Customised First Level Customised Second Level Customised Third Level Hole Hole Hole 0836720 FC 27 1B 0 8 0836724 00 77 36 0 8 0836728 5C 3E 1B 0 8 083672C B0 86 36 0 8 0836730 AC 50 1B 0 8 0836734 80 93 36 08
  48. First Level Second Level Third Level Instruction to load a

    level Constant addresses map table Hole Customised level loader Customised First Level Customised Second Level Customised Third Level Hole Hole Hole
  49. First Level Second Level Third Level Instruction to load a

    level Constant addresses map table Hole Customised level loader Customised First Level Customised Second Level Customised Third Level Hole Hole Hole Original Loader 08043B0A mov r4, r0 08043B0C add r0, r5, 4 08043B0E mov r1, r4 08043B10 bl 0805143Ch
  50. First Level Second Level Third Level Instruction to load a

    level (patched) Constant addresses map table Hole Customised level loader Customised First Level Customised Second Level Customised Third Level Hole Hole Hole Original Loader 08043B0A mov r4, r0 08043B0C add r0, r5, 4 08043B0E mov r1, r4 08043B10 bl 0805143Ch Patched Loader 08043B0A mov r4, r0 08043B0C bl 08367610 h 080v3B10 bl 0805143Ch
  51. First Level Second Level Third Level Instruction to load a

    level (patched) Constant addresses map table Hole Customised level loader Customised First Level Customised Second Level Customised Third Level Hole Hole Hole Patched Loader 08043B0A mov r4, r0 08043B0C bl 08367610 h 080v3B10 bl 0805143Ch R0 is the pointer to which the tilemap will be loaded. We should update it to the address of the custom level
  52. First Level Second Level Third Level Instruction to load a

    level (patched) Constant addresses map table Hole Customised level loader Customised First Level Customised Second Level Customised Third Level Hole Hole Hole Patched Loader 08043B0A mov r4, r 0 08043B0C bl 08367610 h 080v3B10 bl 0805143Ch
  53. First Level Second Level Third Level Instruction to load a

    level Constant addresses map table Hole Customised level loader Customised First Level Customised Second Level Customised Third Level Hole Hole Hole 08367610 mov r0, r15 ; r15 = PC
 08367612 add r0, 3Ch
 08367614 bx r0
  54. First Level Second Level Third Level Instruction to load a

    level Constant addresses map table Hole Customised level loader Customised First Level Customised Second Level Customised Third Level Hole Hole Hole 08367650 add r0, r4, 4 h 08367654 ldr r4, [r15, #-3Ch ] 08367658 cmp r0, r 4 0836765C ldreq r0, [r15, #-40h ] 08367660 ldr r4, [r15, #-40h ] 08367664 cmp r0, r 4 08367668 ldreq r0, [r15, #-44h ] 0836767C ldr r4, [r15, #-44h ] 08367680 cmp r0, r 4 08367684 ldreq r0, [r15, #-48h ] 08367688 mov r4, r 1 0836768C bx r14
  55. First Level Second Level Third Level Instruction to load a

    level Constant addresses map table Hole Customised level loader Customised First Level Customised Second Level Customised Third Level Hole Hole Hole 08367650 add r0, r4, 4 h 08367654 ldr r4, [r15, #-3Ch ] 08367658 cmp r0, r 4 0836765C ldreq r0, [r15, #-40h ] 08367660 ldr r4, [r15, #-40h ] 08367664 cmp r0, r 4 08367668 ldreq r0, [r15, #-44h ] 0836767C ldr r4, [r15, #-44h ] 08367680 cmp r0, r 4 08367684 ldreq r0, [r15, #-48h ] 08367688 mov r4, r 1 0836768C bx r14
  56. First Level Second Level Third Level Instruction to load a

    level Constant addresses map table Hole Customised level loader Customised First Level Customised Second Level Customised Third Level Hole Hole Hole 08367650 add r0, r4, 4 h 08367654 ldr r4, [r15, #-3Ch ] 08367658 cmp r0, r 4 0836765C ldreq r0, [r15, #-40h ] 08367660 ldr r4, [r15, #-40h ] 08367664 cmp r0, r 4 08367668 ldreq r0, [r15, #-44h ] 0836767C ldr r4, [r15, #-44h ] 08367680 cmp r0, r 4 08367684 ldreq r0, [r15, #-48h ] 08367688 mov r4, r 1 0836768C bx r14
  57. First Level Second Level Third Level Instruction to load a

    level Constant addresses map table Hole Customised level loader Customised First Level Customised Second Level Customised Third Level Hole Hole Hole 08367650 add r0, r4, 4 h 08367654 ldr r4, [r15, #-3Ch ] 08367658 cmp r0, r 4 0836765C ldreq r0, [r15, #-40h ] 08367660 ldr r4, [r15, #-40h ] 08367664 cmp r0, r 4 08367668 ldreq r0, [r15, #-44h ] 0836767C ldr r4, [r15, #-44h ] 08367680 cmp r0, r 4 08367684 ldreq r0, [r15, #-48h] 08367688 mov r4, r 1 0836768C bx r14
  58. First Level Second Level Third Level Instruction to load a

    level Constant addresses map table Hole Customised level loader Customised First Level Customised Second Level Customised Third Level Hole Hole Hole 08367650 add r0, r4, 4 h 08367654 ldr r4, [r15, #-3Ch ] 08367658 cmp r0, r 4 0836765C ldreq r0, [r15, #-40h ] 08367660 ldr r4, [r15, #-40h ] 08367664 cmp r0, r 4 08367668 ldreq r0, [r15, #-44h ] 0836767C ldr r4, [r15, #-44h ] 08367680 cmp r0, r 4 08367684 ldreq r0, [r15, #-48h ] 08367688 mov r4, r1 0836768C bx r14
  59. 1. Redirect to the patched code 2. Check if the

    R0 is on the table 3. If yes, load the associated value 4. Return to the original loader
  60. MATHEUS ALBUQUERQUE Special thanks for helping to develop the frontend

    at this talk ythecombinator www.ythecombinator.space