Skip to content

Pokemon TCG ROM Hacking

Posted on:June 30, 2023 at 03:58 AM

I’ve been working on thickening out my Pokémon Gameboy collection, and beating the Pokémon Stadiums. I was trying to obtain another copy of Pokémon Crystal for my sister to coop with me. Its really expensive… So I looked into flashing my own cart, but got distracted by Crystal Clear and then decided to play around with ROM hacking.

Anyway, this is like project #255 for me. We’ll see how far I get before I burn out.

(Side note: Today I thought about dedicating time to this project in hours. 25 hours into Pokémon Red gets me to badge 6. Would I be willing to spend 25 hours working on this? Putting my time in relationship to the games I play makes the time seem like an easier commitment.)

When I started out I had some basic goals in mind:

The first three were surprisingly easy for me. I was able to finish those within a few hours once I got my environment setup. The ones with asterisks were my best guesses on what would be the most difficult for me to do at my skill level. Reusing existing code and tweaking it taught me about the file structure of the disassembled ROM.

added mert Injected “Mert” into game text.

free_booster_packs.png Injected free booster packs into the Tech NPC above!

Creating new functions and declaring new address space as someone who hasn’t worked in assembly for 8 years… was really difficult for me. I’m still stuck and I’m not sure if goal #4 was balanced in hindsight.

The Problem: GBC ROMs have limited space in memory. In the Pokémon TCG NPCs are declared in memory banks. This memory bank has a limited size and is difficult to extend. Or at least I think so. I don’t know what I’m doing. So I thought instead of extending it, why don’t I just used one of the NPCs that is declared as unused?

For example:

	const NPC_05                          ; $05 (unused)

Then I declare a header for this NPC:

NPC05NPCHeader:
	db NPC_05
	db SPRITE_OW_TECH
	db SPRITE_ANIM_LIGHT_NPC_UP
	db SPRITE_ANIM_BLUE_NPC_UP
	db $00
	dw Script_NPC_05
	tx NPC_05NPCName
	db $00
	db $00
	db $00
	db $00

You can see that I have a Script_NPC_05 & NPC_05NPCName. These do work with other NPCs so I won’t bother listing that code here.

Finally, I assign this NPC into the worldspace:

MasonLabNPCS:
	db NPC_DRMASON, $0e, $06, SOUTH
	dw Preload_DrMason
	db NPC_SAM, $04, $0e, EAST
	dw Preload_Sam
	db NPC_TECH1, $16, $08, WEST
	dw NULL
	db NPC_TECH2, $16, $14, SOUTH
	dw NULL
	db NPC_TECH3, $16, $16, WEST
	dw NULL
	db NPC_TECH4, $0a, $16, EAST
	dw NULL
	db NPC_05, $0a, $08, EAST   <--- These two lines
	dw NULL <--- These two lines
	db NPC_TECH5, $06, $04, SOUTH
	dw Preload_Tech5
	db $00

So what does this actually do? At this point I already had defeated Sam, collected my first deck, and saved. I expected a new tech sprite to appear near the professor that would execute my script when I A’d him. This is what I actually got:

double trouble WHAT?! Why are there two of you!? Why do you say the same thing? This has nothing to do with the sprite I chosen or the script that I made.

What’s even weirder is that if I do the same process with NPC_6F (which is also marked as unused) I get an invisible collision square that I can’t interact with. Its almost like these NPCs aren’t pointing to the header I defined for them.

Anyway, I still don’t understand how all this address space is applied. I don’t understand how these NPCs load in the sprite and header information. I’ve proven I’m able to apply new scripts to the existing NPCs. I feel like I’ve hit a point where it might make sense for me to brush up on my assembly. See ya next time!

If you want to see the Pokémon TCG repo you can check it out here: https://github.com/pret/poketcg