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

The day I reverse engineered a Gameboy Advance game [ReactSP#35]

The day I reverse engineered a Gameboy Advance game [ReactSP#35]

Gameboy Advance was one of the most popular video game platforms of its time and, because of it, many people worked together as a community to study and to document its architecture and develop ROM hacking and other interesting tools.

It turns out that this video game is a fantastic way to start studying reverse engineering: its architecture is very well documented and simpler if compared to the current game console generation – and, of course, it is very fun to work in a game-related project.

So... what do you think of learning reverse engineering through this challenge: developing a level editor for a GBA game called "Klonoa: Empire of Dreams"?
We need to understand the architecture behind ARM hardware, apply reverse engineering in order to discover how the logic of the game works, and then use our front-end knowledge (JS + React.js) to build a level editor.

Bruno Macabeus

June 28, 2019
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 exactly the bridge part, which is the left corner. 9A is the ID of the uninterrupted 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. VRAM pulls the tilemap
 containing exactly what the player has

    to see from somewhere else that has the entire thing.
  5. https://www.coranac.com/tonc/text/regbg.htm Direct Memory Access It is a feature of computer

    systems that allows certain hardware subsystems to access main system memory (random-access memory), independent of the CPU
  6. 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).
  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 a region called Fast WRAM.
  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 OAM (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 OAM (07000000).
  11. 1 2

  12. Here is where Y fixes the Klonoa’s position Here is

    where decides if should fix or not the Klonoa’s position
  13. Let’s find the tilemap on rom THE DAY I REVERSE

    ENGINEERED A GAMEBOY ADVANCE GAME
  14. Swi

  15. Input: Exemplifying. . .
 How divider works on ARM Assembly:

    swi 0x06 R0
 numerator R1
 denominator R0
 numerator / denominator R1
 numerator % denominator R3
 abs(numerator / denominator) Output:
  16. x x x x a x x x x -

    I’m on the tile a
 x x x x b x x x x
 
 x x x x a x x x x "- From the tile a to b there are 9 bytes (= 9 tiles),
 x x x x b x x x x so the length of the tilemap is exactly 9
  17. TilemapLayer globalState 0 0#→Ref Point 0 1#→Ref Point 1 0#→Ref

    Point . . . memo( ) Map TilemapLayer OamLayer Point PointOam PointsTile
  18. TilemapLayer globalState 0 0#→Ref Point 0 1#→Ref Point 1 0#→Ref

    Point . . . memo( ) Map TilemapLayer OamLayer Point PointOam PointsTile DrawingLayer
  19. TilemapLayer globalState 0 0#→Ref Point 0 1#→Ref Point 1 0#→Ref

    Point . . . memo( ) Map TilemapLayer OamLayer Point PointOam PointsTile DrawingLayer
  20. TilemapLayer globalState 0 0#→Ref Point 0 1#→Ref Point 1 0#→Ref

    Point . . . memo( ) Map TilemapLayer OamLayer Point PointOam PointsTile DrawingLayer
  21. 0x10 0x01 0xC0 0x01 0x01 0x01 0x00 0x00 0x28 0x06

    0x68
 0x01 0x01 0x01 0x1C 0x00 0x78 0x08 0x80 0x01 0x01 0x01
 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x01 0x1C 0x00 0x00
 0x00 0x00 0x00 0x00 0x01 0x1C 0x00 0x0E 0x76 0x00 0x00
  22. 0x10 0x01 0xC0 0x01 0x01 0x01 0x00 0x00 0x28 0x06

    0x68
 0x01 0x01 0x01 0x1C 0x00 0x78 0x08 0x80 0x01 0x01 0x01
 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x01 0x1C 0x00 0x00
 0x00 0x00 0x00 0x00 0x01 0x1C 0x00 0x0E 0x76 0x00 0x00 First x position
 First y position
 Second x position Second y position Third x position Third y position Fourth x position Fourth y position Fifth x position Fifth y position Kind
  23. const [ xFirstPosition, yFirstPosition, xSecondPosition, ySecondPosition, xThirdPosition, yThirdPosition, xFourthPosition, yFourthPosition,

    xFifthPosition, yFifthPosition, kind, ] = qunpack.unpack('v2 x4 v2 x4 v2 x4 v2 x4 v2 x5 c', bytes);
  24. const { xFirstPosition, yFirstPosition, xSecondPosition, ySecondPosition, xThirdPosition, yThirdPosition, xFourthPosition, yFourthPosition,

    xFifthPosition, yFifthPosition, kind, } = binary.parse(memory) .word16lu('xStage1') .word16lu('yStage1') .skip(4) .word16lu('xStage2') .word16lu('yStage2') .skip(4) .word16lu('xStage3') .word16lu('yStage3') .skip(4) .word16lu('xStage4') .word16lu('yStage4') .skip(4) .word16lu('xStage5') .word16lu('yStage5') .skip(5) .word8lu('kind') .vars, memory, }))