Hello fellow Pokémon fans, tech enthusiasts, and people looking to do silly stuff! From the same person who brought you the MFA phone: Stupid video game decisions and solutions.
When the new Pokémon Sword and Shield games arrived on the Switch a couple weeks ago, I was as excited as a kid on Christmas. A new adventure with the childhood formula of capturing dangerous creatures in little balls of magic and having them fight to the d… Knockout. But it’s fine, because I love my Pokémon, and they love me.

But this isn’t about Pokémon today, no, this is about open source software, hex editing, WinForms, and immaturity. Let’s step back in time to talk about Pokémon and online.
Back in the days of Pokémon old, you could name your ‘Mons whatever you want. However, with the era of online gaming, that’s just not going to fly. The Pokémon games on the Nintendo DS introduced a profanity filter — otherwise everyone will be trading around Stunfish named … Well, never mind. Point being, kids on the Internet with a free text box is trouble. I found out about the profanity filter fairly quickly. I’m mentally twelve, and so obviously I tried to name something “Jackass,” and got blocked back in the day. I then knew that every Pokémon game had a profanity filter from that point forward. It does not, however, stop me from trying a profane option every now and then just to see if Nintendo/Game Freak let something slip through.
Maturity matters?
Fast forward to Thursday, November 7th, 2019. 9:00pm, Pacific Time. Pokémon Sword is pre-loaded on my Switch. A new adventure is about to start. I’m mashing buttons on the Switch waiting for the DRM to unlock and let me play the game when finally — it happens. The intro movie plays. A crowd cheering in a sports stadium. Gym leaders wearing numbered jerseys. Two contenders walk out into the arena and battle for the championship. This time around in the world of Pokémon, everything’s inspired from sportsball in England. I catch a donkey Pokémon and try to name it “Jackass.” It still won’t let me. I’m still mentally twelve. Fast forward to 11:45pm, I’ve arrived in-game at Motostoke Stadium, ready to start the championship challenge myself. With my starter Pokémon leading the charge, I stroll in like I own the place. This is where everything goes all wrong.
As your character walks up to sign-up desk in the Stadium, a cutscene triggers that eventually leads you into an in-game registration prompt for the challenge. There’s only one field — should be easy, right? My jersey number. How could I mess this up? I can pick any number in the world between 0 and 999 and that’ll get plastered all over my characters uniform, my league card, and show up all over the place online. Now, remember, I’m mentally twelve, so I try a memey number just to see what Game Freak allows. I’m not proud of it, especially because without any confirmation or blocking, it accepts it. What have I done. My jersey is a meme. A meme that I have to share with coworkers. Friends. Family! It’s so dumb I don’t even want to write the number here!
I hadn’t paid much attention to the fact that these are the first Pokémon games with an auto-save feature. I have no idea when I’ve last saved and I have no idea when I’d save next. Do I reset? How much progress do I lose? Can I change my jersey later? This is night one, no one’s that far… But I am playing before anyone else I know! I have to stay ahead! What do I do!?
I go to sleep. I have work in the morning. This is a problem for later.
Time To Change

Fast forward to later. Over two weeks later with 23 hours of play time on this save. I had completely forgotten about my jersey number — or rather it didn’t bother me. Who cares? Until I went to share my league card with a coworker. It had to change. To fix this, I had to start by updating my Switch’s custom firmware, Atmosphere. I’ve softmodded my Switch in the past, so this was a quick and painless process. After opening up the Switch, my first instinct was to use PKHeX. I heard that the PKHeX team updated the save editor to support Sword and Shield. Excellent! I’ll just change the trainer info and … It’s not a supported value yet. I can change my money, my name, all my monsters, but not my jersey number. Foo.
But I’m not deterred. Using Checkpoint, I exported my save anyway. How hard could it be to change a single value in the save? Examining the file using a text editor was the expected mess of characters you’d expect when editing a binary file with a text editor. Of course it wasn’t just a JSON blob. I had no idea where to start within my own save, so I immediately decided that if I was going to change my jersey number, I had to start a new save file. I hatched a plan. I’d speed through the beginning of the game, staying as basic as possible, and save right before I pick my jersey number. It only took me 43 minutes and 11 seconds to get to the point of choosing my jersey number again. I closed the game. I backed up my new save, restarted the game, and picked a number. 999. Simple. Using Checkpoint again, I backed up the save with a number, then restored the original numberless save. I picked a new number. 000. Polar opposites. I saved and backed up the file, FTP’d them off to my desktop for forensics.
The file format is still completely unreadable. I honestly have no idea what I’m looking at, but I don’t need to. I used cmp -l
to diff the two binary files, and I’d just look for a value or two that was different between the two saves.
$ cmp -l 000.sav 999.sav | gawk '{printf "%08X %02X %02X\n", $1, strtonum(0$2), strtonum(0$3)}' <br> 0005AF94 27 C7<br> 0005AF95 BA F5<br> 0005AF96 27 71<br> 0005AF9D 17 58<br> 0005B102 2F 3E
What? Why was there so much change? I have zero experience dealing with binary data, this is a headache. So many different ranges were different. The two saves should be identical other than the number I changed and the checksum! What’s all this other cruft? I tried searching through the diff, looking for the obvious. 0’s changed to 9’s. None. Maybe it’s an integer index? 0 to 999 is a thousand values, right? So I looked for 0x3E8 — 1000. Nothing. I then realized that there’s more than a thousand values to choose from. 009 is different from 9. As is 0, 00, and 000. So I gave up on trying to guess on what the value might look like, and generated more data. Three more saves. I decided to do a three-way diff between the saves with jersey numbers 0, 000, and 999 using vim -d

vim -d
with three xxd
-parsed dump filesI didn’t know it at the time, but the value I wanted to change right under my nose. However, none of the values look at all like 0, 000, or 999. And so defeated, I was about to give up. One more brain wave. PKHeX could edit SOME trainer data, and theoretically, all trainer info would be close together in the save dump. I took one of the saves, made a copy of it, changed the trainer name on one save from “Josh” to “Josi”, and ran the diff. Bingo! Easy change detected — one byte changed, and the checksum. At the offset near 0x165550, we find the location of the trainer’s name.
Using this as a base to search for more changes, I found a very striking change right above it in my three-way diff that had previously gone un-noticed. At offset 0x165490, there’s a diff in all three files (and a secret fourth one I periodically check against for patterns):
Save name | Data @ 0x165490 |
0.sav | faa0 ecbf 9d64 9759 0369 b802 fde5 6974 |
000.sav | faa0 ecbf 9d64 9759 0369 b832 cde5 6974 |
420.sav | faa0 ecbf 9d64 9759 0369 bc30 cde5 6974 |
999.sav | faa0 ecbf 9d64 9759 0369 b13b cde5 6974 |
Curious, but I couldn’t understand the format. None of these look like numbers I recognize. On a whim, I looked at the same offset in my actual save file, with 23 hours of gameplay:
main.sav | faa0 ecbf 9d64 9759 0368 bc30 cde5 6e74 |
I recognize that value! Excited, I assumed that this was, in fact, the trainer jersey number, and some other trainer data. The exact offset at 0x16549a in my test saves match the stupid number I picked originally. I have no idea how “0” converts to 802f
or “999” converts to 13bc
but, who cares, I found it. I changed it to 832f
which converts to just “00”. Somehow.
Checksums…

I knew the entire time that Game Freak creates checksums in the save to ensure a corrupted save isn’t written out and re-loaded. Not doing so potentially loses hundreds of hours of gameplay and thousands of unique captured Poké-friends. This is, however, troublesome for an amateur at reverse engineering foreign binaries. I also knew that I didn’t have to worry about the problem because there was a very small chance that I’d even make it this far.
I made it this far, which was not part of the plan. My single-minded thought process then became: just to toss it into PKHeX to re-calculate the checksum. However, PKHeX rightfully and fully validates the save before loading it. Great. I toss the save onto the Switch anyway just to double check it won’t load, but instead of telling me the save is corrupt, please start again (RIP my Pokémon Gold save), the Switch just closes the game with a generic software error message. Neat. Also not helpful.
Maybe I can compute and write it out myself? “How hard could that be?” I say. Again. I’m dumb.
Here is the code that computes the checksum of the save. After reminding myself that:
- I don’t know C#
- While it’s easy enough to read, I don’t fully understand the format the checksum is supposed to be written in
- I’ve spent at least 3 hours trying to change a number on a jersey in a game on a save I was going to throw away
… I abandoned the idea.
I still had PKHeX and its source code in front of me — it has the checksum creation code still! All I need to do is disable the validation on load. There’s even build instructions on PKHeX’s README.
HoW hArD cOuLd ThAt Be?/?
After fighting with Visual Studio 2017 CE to make it understand that .NET Framework is indeed installed, taking a break to bake and eat a pizza, I finally upgraded to Visual Studio 2019 CE. This worked swimmingly. I successfully built and started debugging an unmodified version of PKHeX as it loaded my modified save file. Coming from Java and IntelliJ, using Visual Studio 2019’s debugger felt right at home. Once I finally found where the program entrypoint was (the WinForms part of the project was not that place), it was quick to track down the file load prompt and drop down a breakpoint. From there, I just stepped through the running code until I found where it was actually running the checksum validation. And stubbed it out.

All done…?
I saved out the file with the correct checksum using PKHeX and popped the save onto my Switch. When the Switch sat longer than usual on the game loading screen, I was a little worried. This is what happened when the checksum failed before. Suddenly, the Joy-Con vibrated and the title sequence filled the screen. Had it worked? I quickly button mashed until I could rush into the menu only to find my stupid memester number still stuck to my trainer card. My heart sank. I had spent hours on this. This stupid number. Was the pause just the game restoring the unmodified backup save that’s also kept alongside the main save? Why hadn’t that happened before when the game closed itself? I then remembered that the trainer card data is regenerated each time the character changes clothes. In one last fit of hope, I tried to update my league card. It had worked. I did the thing. I AM A HACKER GOD!
tl;dr
- I made a dumb vanity decision in the game and didn’t want to live with it
- I softmodded my Switch to export the save game
- I spent hours reverse engineering Pokémon saves in the worlds most boring game of “spot the difference”
- I took a hex editor to the binary save
- I dove through the source of PKHeX
- Built a custom version of PKHeX to disable checksum validation on load & rewrite mine correctly
- BECAUSE I WANTED TO CHANGE A STUPID VANITY NUMBER IN THE SAVE FILE
I’m going to go play Pokémon now. Sheesh.
-
Why do I do this -
But I can fix this -
My team of Eevees
Post Scriptum
If you want my league card, I have no idea what they’re for but here:
0000 0006 FTP5 KY
As eluded to earlier, my Pokémon characters tend to get iterated over fairly quickly, but after all that me and this character have gone through, I might just keep it around.
Disclaimers and shoutouts
- Save editing, PKHeX, and Atmosphere, while used responsibly, are great for reasons like changing cosmetics for local, single player games. Don’t use them for cheating. Cheating is sad, bad, and makes Ninty mad.
- I’m not entirely sure how to interpret the output from
xxd
, so my offset locations might be wrong. - I do not think I am a hacker god.
- I didn’t give a huge attempt at understanding the endianness or other formatting of the data. I had pizza to eat and Pokémon to play, and assumed that whatever standards I might know, there’d be some stupid confusing binpacked number at the end of the rabbit hole. Best to yoink the data I want out of a different save and plonk it into mine and use code that’s already written.
- Shout outs to the Nintendo homebrew community, and specifically the PKHeX and Atmosphere team (who helped update PKHeX for Sword/Shield)
- Shout outs to Game Freak for creating yet another wonderful Pokémon experience!