Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ACCFMail

Documentation and tooling for the encryption used on player-to-player WiiConnect24 mail attachments in Animal Crossing: City Folk / Let's Go to the City (RUU*).

This is not the DLC distribution format. Server-sent DLC (RVFOREST / rvforestdl) is a U8 archive signed with an RSA key and encrypted with a fixed AES key — a completely separate mechanism handled by tools like ACWC24/ACDLC. What is described here is the attachment blob that one player's console produces and another player's console consumes when a letter with an attached item, pattern or NPC travels over WC24 mail.

TL;DR

The attachment is AES-128-CBC, but there is no fixed key in the game. The key and IV are generated randomly per message and appended to the ciphertext in the clear. The only thing binding the blob to a sender is the sender's 16-digit Wii Number, which is mixed into both the AES key and the HMAC key — and which you already have, because it is the WC24 mail's From: address (w<16 digits>@wii.com).

So "cracking" it needs no secret. You need the envelope.

Blob layout

N is the ciphertext length, always a multiple of 16. Total file size is N + 0x14.

Offset Size Contents
0x00 N AES-128-CBC ciphertext
N 0x10 IV — 16 random bytes, stored in the clear
N + 0x10 0x04 R — 4 random bytes, stored in the clear

Key and IV

key = R (4 bytes) || u64be(sender_wii_number) (8 bytes) || 71 71 71 71
iv  = the 16 bytes stored at offset N

R is four bytes of RNG output stored raw. The trailing 71 71 71 71 (ASCII qqqq) is a hardcoded constant. The middle eight bytes are the sender's NWC24 Wii Number as a big-endian u64 — the same value that appears as the 16-digit decimal From: address. (The in-game default before a Wii Number is provisioned is 9999999999999999, i.e. 00 23 86 F2 6F C0 FF FF.)

Plaintext

0x00  u64be   sender Wii Number     (must match the envelope, or the game rejects it)
0x08  char[8] "BU2mori\0"           magic
0x10  u32     payload length
0x14  u32     version               always 2
0x18  u16     (blob_count << 14) | offset_of_second_payload
0x1A  u16     stationery / letter type
0x1C  u32     padding
0x20  ...     payload[length]
...           zero padding to N - 0x18
N-0x18 u32    S — HMAC salt, 4 random bytes
N-0x14 u8[20] HMAC-SHA1

The digest covers plaintext[0 : N-0x18] — i.e. everything before the salt — using a 20-byte HMAC key:

hmac_key = u64be(sender_wii_number) || S || 45 30 39 CB FE 19 DA 72

N is derived from the payload length as N = (length + 0x47) & ~0xF, which is align16(0x20 header + length + 0x18 trailer).

blob_count in the field at 0x18 is 1 or 2. When it is 2, the low 14 bits give the offset within the payload at which the second blob starts (item vs. pattern, for example); when it is 1 the payload is a single blob and the offset is 0.

The u16 at 0x1A lands in the same numbering space that the DLC tooling uses for stationery, where a paper id is index + 400 over the standard 69-entry paper table (butterfly = 400 … bulletin = 468). A value of 424 therefore corresponds to Nook stationery. This mapping is consistent but has not been exhaustively confirmed against every attachment kind, so treat it as strongly suspected rather than proven.

The decrypted payload itself (town/player records with UTF-16BE names and u16 ids, the letter header/body/footer, then a short trailer) is only partially mapped and is not documented here.

Where this comes from

Reverse engineered from the USA main.dol (RUUE01). Useful anchors:

Address Role
0x800EA2C8 pack + encrypt an attachment
0x800EA584 decrypt + verify an attachment
0x800EA0DC mailbox scan — sender app id RUU\0, attachment type 0x30000, 0x25800 byte cap
0x80414948 NETAESCreateEx(ctx, key, keylen, iv)
0x80414954 AES-CBC encrypt
0x80414A0C AES-CBC decrypt
0x80413D44 NETHMACInit(ctx, alg, key, keylen)
0x80497110 hash descriptor {digest 0x10, block 0x40, …} — MD5
0x80497130 hash descriptor {digest 0x14, block 0x40, …} — SHA-1, used here
0x80497150 AES S-box (byte-oriented software AES, no T-tables)
0x80750930 the "BU2mori\0" magic, at r2 - 0x7410

r13 (SDA) is 0x807516C0 and r2 (SDA2) is 0x80757D40, if you need to resolve other small-data references while poking at this.

Only three constants are actually hardcoded: 71 71 71 71 (AES key tail), 45 30 39 CB FE 19 DA 72 (HMAC key tail), and the BU2mori magic.

Usage

pip install pycryptodome
python3 accf_attach.py <attachment.bin> <sender_wii_number>

Writes <attachment.bin>.dec containing the payload, and prints the parsed header. The HMAC is verified, so a wrong Wii Number fails loudly rather than producing garbage.

accf_attach.py also exposes encrypt(payload, wii_number, typ, second_offset, count) for producing blobs the game will accept.

Status

Validated against a real attachment captured from a console: HMAC-SHA1 verified and the payload decoded cleanly. Round-trips through encryptdecrypt as well.

Contact

General questions or comments can be sent to quatricsoftware@gmail.com. No support will be provided for this tool.

Credits

The stationery id mapping (paper id = index + 400) comes from Aurum's ACWC24, which covers the unrelated server-side DLC distribution format for the same game.

License

MIT — see LICENSE.

Copyright (c) 2026 quatric

About

Encryption of player-to-player WiiConnect24 mail attachments in Animal Crossing: City Folk

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages