Animal Crossing: amiibo Festival (Wii U) — patch to remove Katrina

Luke G.
Admin
 Posted 2 weeks ago
 

Animal Crossing: amiibo Festival (Wii U) is intended to be a family-friendly, adorable board game for people of all ages to enjoy. For me, however, the board game mode crossed the line with frequent, forced tarot card readings by a fortune teller. Here's the journey it took to create a fix.

For me, I do not wish to involve myself with fortune tellers and tarot card readings. It is my position that a person's spiritual choices involving whether or not they and their children should be a part of occult or pagan activities should not be imposed on nor dictated by a video game company. If you are instead fine with that, or love tarot cards and fortune telling for you and your children, then this article and related patch are obviously not for you. You might enjoy the technical explanations below, but your time is probably better spent elsewhere, to be honest.

I've disabled comments on this post because I've based this idea off of my own values and I'm not looking for a discussion of their inherent validity.


You might want to see my review of this game for an explanation of what it is, if you haven't already read it. My focus in this article is on the problem I ran into, and how I managed to fix it.


The problem

At the start of my first playthrough, I found the game to be overly cute and quite wholesome. Everyone's interactions were polite and friendly, and it was relaxing to not have the pressure of full-on competition and worrying if another player would steal my money. Everything went well until the Animal Crossing fortune teller Katrina came prowling out of the shadows for a random event. She tells you that, if you land on her space, you must visit her. She means that quite literally, as once you land on her space, you absolutely cannot leave her tent without picking a tarot card from her deck and having it read to see what your fortune will be.

The tarot cards are based on cards from the real life 'major arcana' tarot deck, but with Animal Crossing characters playing the roles of the figures on five of the cards. Each card has an effect that lasts for one in-game week, such as, when someone rolls a particular number you get 200 bells. If the card is read upside down, then a negative effect happens to you instead. I understand the intent of wanting to add some randomness to the game, but I absolutely detest the means of doing it.
Katrina's event appears Katrina's instructions Katrina's instructions Katrina's instructions Katrina's instructions


The idea

We live in an age of some pretty advanced game modification techniques (called ROM hacking). Some games (referred to as ROMs) have 100s of these modified versions, wherein you might play as characters from other franchises, play a more challenging set of levels, have to navigate the game with all items randomly moved from their original places, and so on. These changes are applied via 'patches' that are downloaded and applied to the original game ROM to replace relevant parts of the game's programming code.

My idea was to see if it would be possible to determine the logic that triggers Katrina's random appearance, and then find a way to stop it. The thing blocking me on that is that I only ever produced one patch, and it was based on existing work done by someone else. While I am a software developer, I have no experience decompiling a game and trying to read highly obfuscated code that only really makes sense to the computer. This task would take years to learn, and even then, there'd be no guarantee I could find and fix my issue.


The plan

For better or for worse, AI is here and begging to be used for everything from shopping to computer programming. As many do today, I use it in my job for some tasks, although it requires a certain amount of hand-holding and cheerleading to get the job done right. On a whim, I presented my AI agent with an in-depth explanation of my request just to see if it could at least point me in the right direction. The result was a discussion that spanned over two weeks and resulted in a final solution that solved my issue years sooner than I could have done myself!

The details to follow, as well as the ultimate fix, involve needing a Wii U that is running custom firmware (CFW), also known as being soft-modded. While it should also be possible to play using a Wii U emulator such as Cemu, the latest versions as of this writing are not compatible with this game. Without one of those two options, there is no way to make changes to the original game programming. Both of those options are beyond the scope of this article, and information about them can be readily found online elsewhere.


I will start by saying that the AI agent itself gave up at least five times. In one testing endeavor, at my suggestion, we disabled every random event in order to see if we were on the right track at all. Each time we hit a roadblock after that, the agent strongly suggested just leaving everything disabled and being satisfied with that. I pushed back each time that I really only wanted Katrina to be disabled, until it finally found a working solution.

With that behind us, it recommended a plan of in-game testing that would have taken me 12 hours to do. I suggested a solution that took about two hours instead. So, this process was not simply a matter of throwing together 'AI slop' and letting it take the lead in this endeavor, but I had to actively direct the agent as to how to proceed, what to try, and how to test it, based on my own knowledge of the field and experience. And test it I did! In addition to hours of tests during the process of determining a potential fix, I ran at least five one-hour board game sessions on the final patch, and Katrina never appeared. So, I'm very, very confident that this fix works!

I asked the AI agent to summarize the steps taken from the beginning of the project to its completion, and below is the short version of what happened. Following this, I will supply the technical explanation it also provided for me. If you are not a software or game developer, that section will probably not be meaningful to you, and might cause minor brain bleeding. The patches to fix or revert the fix are found at the end of this article, so feel free to jump there instead of reading the technical details.




The simple version (in the words of my AI agent)

Animal Crossing: amiibo Festival is a Wii U game where, every so often, a random special character shows up to do something unique — K.K. Slider plays a concert, Redd sets up an art sale, Pascal shows up on the beach. One of these random visitors is Katrina, a fortune-telling panther common to the series, and for reasons that are entirely a matter of personal taste, Luke wanted her gone. Permanently. Every other random visitor could stay; just not her.

There's no in-game setting for this. So the only way to do it was to open up the compiled game code itself, figure out how the game decides "who shows up today," and surgically remove just her from that decision — without touching anything else, and without breaking the game.

That turned into a much deeper rabbit hole than expected: multiple wrong guesses that looked right until tested, a couple of real bugs in my own patches, live-hardware testing over dozens of in-game days, and eventually a small binary patch that does exactly one thing: it makes the game treat Katrina's specific "slot" in its daily lottery as permanently empty.

The result is two small files — a patch and its exact undo — that anyone with a legally-dumped copy of the game can apply to bring about the same result, plus a full write-up of how it was found, for anyone curious about what that process actually looks like on a Nintendo console from the inside.




The technical deep dive version (in the words of my AI agent)

The game and the toolchain

Animal Crossing: amiibo Festival (Wii U, title ID 00050000101C6400, US region) ships as a set of Cafe OS executables: a main bbq.rpx and several .rpl library modules, one of which — board.rpl — handles the party-board game logic. Both are ELF-derivative files in Nintendo's "RPL" format: mostly-ordinary ELF, except individual sections can be independently zlib-compressed, and there are a handful of Cafe-specific section types (SHT_RPL_EXPORTS, SHT_RPL_IMPORTS, SHT_RPL_CRCS) for cross-module linking between the main executable and its .rpl libraries.

None of the existing tooling I found did quite what I needed, so I ended up writing a small Python toolkit from scratch: an RPL/ELF parser that handles the zlib sections and the export/import tables, a parser for Nintendo's proprietary "PAC" archive format used for the game's asset bundles (shared with Mario Party 10, as it turns out — both are ND Cube titles), an MSBT localized-text parser, and a Capstone-based PowerPC disassembler with relocation-aware call-graph tracing (cross-module calls show up in the raw file as placeholder addresses that only resolve correctly once you follow the ELF relocation table).

First guess: VisitorId 31 (wrong)

The compiled binary still had its debug symbol table intact — a huge convenience, since it meant real, demangled C++ function and class names instead of a sea of anonymous addresses. Searching for anything related to "Katrina" led to bd::TodaysHappeningEvent::canAllowVisitorToCome(int), a function that reads a per-character enable flag out of a small table. A message-file search turned up bbq_SPNpc_name031 → "Katrina", a special-NPC name list where the numeric suffix in each symbol was literally the character's ID. 31 looked like the answer.

It wasn't. Patching canAllowVisitorToCome to reject id 31 changed nothing — she kept showing up. Rebuilding and testing this patch also surfaced a real bug in the RPL format's compression handling: the Cafe loader reads *exactly* section_size - 4 compressed bytes and feeds them to inflate() directly — it does not stop at the natural end of the deflate stream the way most zlib wrappers do. My first repack attempt zero-padded the leftover space after recompressing (to avoid moving every subsequent section in the file), which caused the loader to choke on the trailing garbage and refuse to boot the title at all ("executable is damaged" in Cemu). The fix was to shrink the section header's declared size to match the new, smaller compressed length instead of padding — a good reminder that "it decompresses fine in Python" and "the actual game loader is happy with it" are not automatically the same thing.

Second guess: TodaysHappeningEvent id 101 (also wrong)

With the loader bug fixed, the patch now *loaded* — but Katrina still appeared. Time to reconsider the actual ID. The canAllowVisitorToCome signature takes a TodaysHappeningEvent value, a different enum from VisitorId. Digging through the binary's own debug label tables (an internal getTdEventLabel(int) → "td_all_NNN" lookup, seemingly meant for developer logging) turned up td_all_101, and the corresponding localized text file for that ID — td_all_101.msbt — contained the string "Katrina space." Convincing evidence. Rebuilt the patch for id 101.

Still wrong. She still showed up.

Finding the real mechanism

At this point it was clear canAllowVisitorToCome simply wasn't the actual decision point — cross-referencing its callers confirmed it's a real, legitimately-used cross-module function (board.rpl imports and calls it exactly once), but only as one of several downstream validity checks (alongside weather and time-of-day conditions) on a value that had already been decided somewhere else.

Tracing backward through board.rpl eventually surfaced the real mechanism: once per board-game session, the game runs a weighted random lottery over a 120-slot candidate table to decide "today's happening." Each 36-byte record holds an enable flag, a lottery weight, a valid month-range, and some weekday-type flags. The winning slot's raw index (0–119) is the TodaysHappeningEvent value used everywhere else in the game — no translation step. The debug label naming (td_all_101) and the actual runtime index turned out to be two different numbering schemes entirely; directly reading the binary's static id→string lookup array and searching for which index pointed at the Katrina string revealed the real value: the td_all_1NN numbers map to real indices via a flat -20 offset (td_all_100 = index 80, td_all_101 = index 81, and so on) — not something documented anywhere, just something that fell out of reading the compiled table directly.

Testing it — the empirical part

Static analysis got the mechanism and a candidate index (81), but the only way to be sure was to test it on real hardware, and that meant building several disposable test patches:

  • A patch that disabled all 120 slots at once, as a sanity check that this table really was the mechanism. Played a full session: every calendar-fixed holiday and the deterministic weekly NPC still appeared normally, while every random event — Katrina included — never did. Confirmed the mechanism.
  • A binary search across ranges of the 120 slots (quarters, then eighths), each requiring a real multi-day play session to get a signal, narrowing candidate 81 down from "somewhere in 120" to "confirmed."
  • A "keep only this one slot active, disable all 119 others" test, run against slot 81 for a full one-hour session: Katrina appeared 5 times, nothing else random did. A second version of this same test, applied to Redd's and Phineas's candidate slots (found the same way — searching their names in the game's own text files) came up empty even after fixing a real bug in the test patch itself (the first version only bypassed the enable-flag check, not the month/weekday checks further down the same code path, which could mask a real hit if the real-world calendar didn't line up) — suggesting those two specific events have some additional gating condition never fully traced. Katrina's slot had no such issue.
  • Finally, the actual intended patch — disable *only* slot 81, leave the other 119 fully intact — survived a complete real game session with Katrina never appearing and every other random event continuing to work normally (if anything, slightly more often, consistent with her removed weight being redistributed across the remaining candidates in the lottery).

Every patch, at every stage, was built via a small "code cave" technique: replace one instruction at the check site with a branch to newly-appended code at the end of the (now slightly larger) compiled section, run the real logic there, then branch back — letting a couple of extra instructions' worth of new logic get inserted without disturbing anything else in the file. Every hand-encoded branch offset was double-checked by disassembling the result and confirming it landed exactly where intended — I made the same kind of off-by-a-few-bytes mistake more than once by trusting mental arithmetic instead of a disassembler round-trip, which is a good general lesson for anyone doing this kind of binary patching by hand.

The deliverable

The final patch is a single 4-byte instruction replacement at one specific address in board.rpl's compiled code, expressed as a standard IPS patch (plus an exact reverse/undo patch), along with the hashes needed to confirm it's being applied to a matching, unmodified file before use.

What's still unresolved

The function that actually *populates* that 120-slot table at runtime was never found, despite a fair amount of searching — it's likely filled in via a generic "load resource into buffer" routine rather than anything with an obviously-searchable, hardcoded address. And there's currently no reliably working tool for live memory inspection on Wii U's current Aroma homebrew environment (several options were tried; one didn't connect, one doesn't compile against the current plugin library version, one turned out to be an unrelated tool entirely) — which would have made some of the binary-search testing above considerably faster if it had worked.

Final note

If you're looking to do something similar with a game you own: this whole process amounted to combining static analysis of the compiled code with patient, repeated empirical testing on real hardware, and being willing to throw out a confident-looking guess the moment it failed a real test.




The patch and how to use it (partially composed by my AI agent)


Download Download: NoMoreKatrina.zip


First things first: I am not responsible if you brick your Wii U.

To proceed, you are expected to know how to safely 1) FTP into it, 2) navigate to a specific directory, 3) download a file from your Wii U to your computer, 4) apply an IPS patch, and 5) upload that patched file back to the proper location on your Wii U.

None of that is inherently going to break your console. However, if you aren't confident, then getting some help from someone who is more adept at these things is recommended. The risk of bricking a console is there with any system-level file access, so just be careful.


Here's all you'll need to know about applying this patch to your legally dumped and installed copy of Animal Crossing: amiibo Festival. These directions are also included in the zip file as DisableKatrina_board_README.md.

Files: DisableKatrina_board.ips / RestoreKatrina_board.ips

These are IPS patches for Animal Crossing: amiibo Festival (Wii U, title ID 00050000101C6400, US region, WUP-P-AALE) that disable Katrina's random fortune-telling event specifically, without affecting any other random event (Redd's gallery, Pascal, K.K.'s concert, seasonal events, tournaments, etc.) or any calendar-fixed holiday.

Before you apply it

This is a raw byte-diff against one specific dump's board.rpl. Because internal zlib compression can vary slightly between different dumping/ extraction passes (same game, byte-different file), applying this patch to a board.rpl that doesn't match the expected source will silently corrupt the file rather than fail cleanly - IPS has no built-in source-file check.

Verify your code/board.rpl matches this hash before patching:
MD5:    96630eafec2796826c0a2ed0ecb51763
SHA256: daaeca938b32775e8eae6dbd4ba8c7d4180cdff04d2677a27dbc4f1bb8617e37
Size:   656,064 bytes
The patched result (for reference, e.g. to verify after patching) matches:
MD5:    7ff2ebb74a08e0c4962be9a23f5f4c0f
SHA256: 135496e9cecade1f22346c444cbcfa89dce6f87b67c2467b1222aff6b9600473
Size:   656,064 bytes

If your file's hash doesn't match the pristine hash above, do not apply it.

Where to find board.rpl on your Wii U (via FTP, e.g. ftpiiu / Aroma FTP)

Titles are stored under a fixed path based on the split Title ID (00050000 / 101c6400) regardless of which storage device they're installed to - only the root differs:

  • Installed to USB drive: /storage_usb/usr/title/00050000/101c6400/code/board.rpl (some FTP servers show this as /storage_usb01/... instead)
  • Installed to internal storage (NAND): /storage_mlc/usr/title/00050000/101c6400/code/board.rpl

How to apply

  1. Back up your original board.rpl first, before touching anything on the live title install. Download a copy over FTP and keep it somewhere safe.
  2. Some patchers (like Lunar IPS) will not copy the file being patched, but instead do the patching in-place. In that case, you would be losing your safe copy of the original file. You might want to make a separate copy somewhere for safe keeping, so you don't lose the original.
  3. Use any standard IPS patcher (Lunar IPS, Multipatch, flips, etc.) to apply DisableKatrina_board.ips to your local copy of board.rpl.
  4. Upload the patched file back over FTP to the same path, overwriting the original.

No decompression or decryption step is needed on your part - board.rpl's internal section compression is already handled inside the patch itself. The only prerequisite is that your board.rpl was already decrypted from your own legitimately-owned copy of the game (i.e. it's the same kind of file you'd get from a normal CDecrypt-style extraction), matching the hash above.

How to undo it (put Katrina back / restore the original file)

Do this if you really miss her, or need to put your installed game back to the original for whatever reason.

  1. Use the same IPS patcher to apply RestoreKatrina_board.ips to your patched board.rpl (the one matching the "patched result" hash above).
  2. This reverses the patch exactly, byte-for-byte, back to the original pristine board.rpl (matching the original hash above).
  3. Upload the restored file back over FTP, overwriting the patched one.

Alternatively, since you already have a backup of the original file from step 1 of the apply instructions, you can simply re-upload that backup directly instead of using the restore patch - both approaches produce an identical result.

Conclusion

And...there you go! Now you too can play Animal Crossing: amiibo Festival without having to interact with Katrina. Maybe you also understand software development and AI's role in it a little better as well.

One note I will add — in case you didn't read the technical description — is that other randomly visiting characters may seem to show up more frequently during a game once you apply this patch. Katrina's slot evidently carried a comparatively large weight in the lottery — disabling it caused other visitors to show up noticeably more often, consistent with her share being redistributed. You may get a little tired of hearing Dr. Shrunk's jokes again and again, but you should see other, rarer characters appear more frequently too. Hmm...maybe my next AI-assisted patch will be to port over some of Dr. Shrunk's jokes from his other appearances into this one...
Even he knows his jokes are a little sad

Related links

 

comments disabled for this post