Research on Animal Crossing: New Horizons design pattern data.
I noticed when using NHSE and different design editors that under certain conditions, importing pattern files (.nhd
or .acnh
) did not seem to set the patterns as owned by the player and thus they were not editable in-game, much like if they were QR codes or from the pattern exchange.
I also noticed that for some pattern slots, the previous pattern name would remain in place of the imported one and both issues bugged me.
So, I forked NHSE, grabbed some dumped patterns and dat files then spent some time and hacked together a fix and added it to my fork, which was successfully merged into the main repo (yay!🥳)... and I will continue to submit finds and fun features to NHSE as it is the best thing out there for goofing with ACNH! 🏝️🤓
Some details are common enough knowledge that they are incorporated into code bases, some aren't (such as the fix), but there doesn't seem to be a consolidated spot for the information.
As a result of looking into it, I was also motivated to document the details and to create some tools to rip, host, convert and catalog design pattern files.
You can find technical detail in the below writeup and/or check out my ACNH Pattern Dump Index repo (WIP!👷).
If any of this has been helpful, please consider caffeinating me further ☕
Thanks!
- ACNH General Design Pattern Info
- ACNH Design Pattern Data
- ACNL Interoperability
- ACNL Pattern Data
- ACNL Pattern Types Values
- ACNL Color Palette Index
- ACNL Palette Common Representation
- ACNL Pattern Conversion Pseudocode
- QR Code Data Information
Design pattern data is found in main.dat
within the save, as are flags for if the player has edited the pattern slot.
An ID for the player and the town/island reside in personal.dat
within the save.
The pattern 'IsEdited' flags begin at offset 0x8BE260
starting with design 1 of 100. Each flag is a single byte and each byte begins as 0xFF
and is changed to 0x00
when the player edits that pattern slot.
Add 0x64
to that offset to get to the design PRO patterns at offset 0x8BE2C4
starting with design 1 of 100. The bytes follow the same pattern as above.
This fixes the issue of imported patterns retaining the name of the pattern originally in that slot.
The issue of pattern ownership and editability is caused by the pattern data not being written to main.dat
with the players IDs correctly, as well as what appears to be a flag value that needs to be reset.
The players ID information can be found within personal.dat
starting at offset 0xB0B8
. It can also be found in other locations, such as main.dat
before the pattern offsets.
It is made up of two primary parts: the PlayerID
and TownID
, which are each made up of two parts; the ID
and the Name
.
The TownID comes first, with the ID starting at offset 0xB0B8
for 4 bytes, followed by the name starting at 0xB0BC
for 20 bytes.
The 20 bytes represent the name as a 10 character long name with 0x00
between each character.
The PlayerID comes next, with the ID starting at offset 0xB0D4
for 4 bytes, followed by the name starting at 0xB0D8
for 20 bytes.
The 20 bytes represent the name as a 10 character long name with 0x00
between each character.
Storing these as a two byte arrays of 0x18
or 24 bytes length starting at each of their respective offsets allows us to then write them into the data for the patterns as we insert them into main.dat
.
The flag value that follows them appears to be 4 bytes long and replacing each byte with 0x00
seems to modify ownership so long as the above ID values match what the game expects correctly.
Patterns in main.dat
start at offset 0x1E3968
and flow into one another.
Complete untrimmed pattern data is 680 bytes long, starting with a 16 byte hash and ending with 3 trailing 0x00
bytes after the image data.
This format matches what you will find in *.acnh
files from https://acpatterns.com/ and *.nhd
files from NHSE, files from other editors may be trimmed.
We can use a *.nhd
file to isolate the data we are interested in.
If we take the PlayerID
and TownID
data extracted from personal.dat
and inject it at offsets 0x54
and 0x38
respectively, then write these back to their correct location in main.dat
(main pattern offset + index) we end up with a pattern written with the image we wanted, and the correct player and town IDs. Then we can overwrite the data at 0x70
with 0x02, 0xEE, 0x00, 0x00
. This allows the user to own/edit them in-game and placed version will maintain their transparency.
When you mix the pattern being updated with the players IDs correctly, and the IsEdited flags flipped to edited you get a correctly named and editable pattern imported into your save. Yay!
PRO patterns follows a similar methodology, but with differing offsets. The above concept applied to them also works.
You can refer to the below for more info.
This was fun to find and fix and I hope it is an educational reference in the future.
The structure of the design pattern file/data is explained below:
Offset & Range | Data Purpose | Data Type |
---|---|---|
0x000 -> 0x00F |
pattern hash - (16 bytes long) | UInt16/UInt32 |
0x010 -> 0x037 |
pattern name - (40 bytes long, 20 char name with separating 0x00 ) |
ASCII/UTF-8 |
0x038 -> 0x03B |
town ID - (4 bytes long) | UInt16/UInt32 |
0x03C -> 0x04F |
town name - (20 bytes long, 10 char name with separating 0x00 ) |
ASCII/UTF-8 |
0x050 -> 0x053 |
padding? - (4 bytes long) | Byte |
0x054 -> 0x057 |
player ID - (4 bytes long) | UInt16/UInt32 |
0x058 -> 0x06B |
player name - (20 bytes long, 10 char name with separating 0x00 ) |
ASCII/UTF-8 |
0x06C -> 0x06F |
padding? - (4 bytes long) | Byte |
0x070 -> 0x073 |
ownership & placement flag - (4 bytes long) | Byte |
0x074 -> 0x076 |
padding? - (3 bytes long) | Byte |
0x077 -> 0x077 |
pattern type - (1 byte long, see below) | Byte |
0x078 -> 0x0A4 |
palette data - (45 bytes long, 15*3, 15 colors 3 bytes each (rgb)) | UInt8 |
0x0A5 -> 0x2A4 |
pixel data - (512 bytes long, pro designs except this, see below) | UInt8 |
0x2A5 -> 0x2A7 |
trailing padding - (3 bytes long) | Byte |
For PRO Design Patterns, the pixel data is longer and is followed by the same termination padding.
See below:
Offset & Range | Data Purpose | Data Type |
---|---|---|
0x0A5 -> 0x8A4 |
pixel data - (2048 bytes long) | UInt8 |
0x8A5 -> 0x8A7 |
trailing padding - (3 bytes long) | Byte |
Below is a visualisation of the example .nhd
included in the repo, with each data section highlighted:
Due to the QR code import in ACNH that supports ACNL designs, interoperability is supported and fairly straight forward.
See below for more information:
Index Value | Type Indicator | Data Type | ACNL Equiv. |
---|---|---|---|
0x00 |
Simple Pattern | Byte | ✔️ 0x09 |
0x01 |
Empty Pro Pattern | Byte | ❌ Not supported |
0x02 |
Simple Shirt | Byte | ❌ Not supported |
0x03 |
Long Sleeve Shirt | Byte | ❌ Not supported |
0x04 |
T Shirt | Byte | ❌ Not supported |
0x05 |
Tanktop | Byte | ❌ Not supported |
0x06 |
Pullover | Byte | ❌ Not supported |
0x07 |
Hoodie | Byte | ❌ Not supported |
0x08 |
Coat | Byte | ❌ Not supported |
0x09 |
Short Sleeve Dress | Byte | ❌ Not supported |
0x0A |
Sleeveless Dress | Byte | ❌ Not supported |
0x0B |
Long Sleeve Dress | Byte | ❌ Not supported |
0x0C |
Balloon Dress | Byte | ❌ Not supported |
0x0D |
Round Dress | Byte | ❌ Not supported |
0x0E |
Robe | Byte | ❌ Not supported |
0x0F |
Brimmed Cap | Byte | ❌ Not supported |
0x10 |
Knit Cap | Byte | ❌ Not supported |
0x11 |
Brimmed Hat | Byte | ❌ Not supported |
0x12 |
Short Sleeve Dress 3DS | Byte | ✔️ 0x01 |
0x13 |
Long Sleeve Dress 3DS | Byte | ✔️ 0x00 |
0x14 |
Sleeveless Dress 3DS | Byte | ✔️ 0x02 |
0x15 |
Short Sleeve Shirt 3DS | Byte | ✔️ 0x04 |
0x16 |
Long Sleeve Shirt 3DS | Byte | ✔️ 0x03 |
0x17 |
Sleeveless Shirt 3DS | Byte | ✔️ 0x05 |
0x18 |
Hat 3DS | Byte | ✔️ 0x07 |
0x19 |
Horn Hat 3DS | Byte | ✔️ 0x06 |
0x1E |
Standee 3DS | Byte | ✔️ 0x08 |
0x1A |
Standee | Byte | ❌ Not supported |
0x1B |
Umbrella | Byte | ❌ Not supported |
0x1C |
Flag | Byte | ❌ Not supported |
0x1D |
Fan | Byte | ❌ Not supported |
0xFF |
Unsupported | Byte | N/A |
The ACNL design pattern data format shares similarities with the ACNH format in that it contains name strings, ID bytes, hashes, pattern type bytes, a color palette and pixel data.
To convert the data between, you simply need to adjust values as required and move the data to the correct offsets.
The structure is per below:
Offset & Range | Data Purpose | Data Type |
---|---|---|
0x000 -> 0x027 |
pattern name (40 bytes long, 20 char name with separating 0x00 ) |
ASCII/UTF-8 |
0x028 -> 0x029 |
padding (2 bytes long) | Byte |
0x02A -> 0x02B |
player ID (2 bytes long) | UInt16/UInt32 |
0x02C -> 0x03D |
player name (18 bytes long, 9 char name with separating 0x00 ) |
ASCII/UTF-8 |
0x03E -> 0x03F |
padding (2 bytes long) | Byte |
0x040 -> 0x041 |
town id (2 bytes long) | UInt16/UInt32 |
0x042 -> 0x053 |
town name (18 bytes long, 9 char name with separating 0x00 ) |
ASCII/UTF-8 |
0x054 -> 0x057 |
unknown flag/hash? (4 bytes long, values seem random, change has no effect) | Byte |
0x058 -> 0x066 |
palette data (15 bytes long, value is an index lookup, see below) | Byte |
0x067 -> 0x067 |
unknown flag? (1 byte long, value seems random, change has no effect) | UInt8? |
0x068 -> 0x068 |
ownership flag? (1 byte long, appears to be 0x00 or 0x0A only) |
UInt8? |
0x069 -> 0x069 |
pattern type (1 byte long, see below) | Byte |
0x06A -> 0x06B |
unknown (2 bytes long, value seems random, change has no effect) | Byte |
0x06C -> 0x26B |
pixel data main (512 bytes long, main pixels) | UInt8 |
0x26C -> 0x46B |
pixel data expanded 1 (512 bytes long, extra pro pattern pixels) | UInt8 |
0x46C -> 0x66B |
pixel data expanded 2 (512 bytes long, extra pro pattern pixels) | UInt8 |
0x66C -> 0x86B |
pixel data expanded 3 (512 bytes long, extra pro pattern pixels) | UInt8 |
0x86C -> 0x86F |
trailing padding (4 bytes long, appears optional) | Byte |
For strings (names), either trim or expand them to match the format (ACNL -> ACNH expand, ACNH -> ACNL trim).
The same can be said for the ID bytes.
Pattern types are cross supported (ACNH has pattern types for the ACNL patterns).
Simply match up the type value correctly from each index.
Index Value | Type Indicator | Data Type | ACNH Equiv. |
---|---|---|---|
0x00 |
Long Sleeve Dress | Byte | ✔️ 0x13 |
0x01 |
Short Sleeve Dress | Byte | ✔️ 0x12 |
0x02 |
Sleeveless Dress | Byte | ✔️ 0x14 |
0x03 |
Long Sleeve Shirt | Byte | ✔️ 0x16 |
0x04 |
Short Sleeve Shirt | Byte | ✔️ 0x15 |
0x05 |
Sleeveless Shirt | Byte | ✔️ 0x17 |
0x06 |
Horn Hat (Simple Pattern) | Byte | ✔️ 0x19 |
0x07 |
Hat (Simple Pattern) | Byte | ✔️ 0x18 |
0x08 |
Standee | Byte | ✔️ 0x1E |
0x09 |
Easel (Simple Pattern) | Byte | ✔️ 0x00 |
The color palette for ACNL is comprised of 159 fixed colors with an index to be looked up, unlike ACNH which supports a wide selection of RGBA colors.
To convert from ACNH to ACNL format a closest matching color function needs to be run against the ACNH color to find the closest one in the ACNL index for each of the 15 colors. The color is then represented in the palette as that single index byte instead of the 3 bytes for RGB used by ACNH.
To convert in the other direction, a straight conversion can be made by taking the index and it's known color value and writing out the three values.
Colors are in blocks of 9 per group from 0x00
-> 0x08
of each offset, with 1/15 of the grey block at 0x0F
of each offset (except for 0xFF).
From 0x09
-> 0x0E
of each offset is unused data.
Pink | Red | Orange | Peach |
---|---|---|---|
|
|
|
|
Purple | Fuchsia | Brown | Yellow |
|
|
|
|
Indigo | Blue | Dark Green | Light Green |
|
|
|
|
Slate Blue | Light Blue | Ocean Blue | Bright Green |
|
|
|
|
Grey |
---|
An example for converting from an ACNH to an ACNL format in C# style pseudocode is as follows:
ACNH-Pattern-Research/acnl_convert_csharp_pseudo.cs
Lines 1 to 203 in fd08e9c
For generation of QR Codes, the design pattern needs to be converted into ACNL format.
For normal design patterns, the QR Code data needs to be encoded in raw bytes (620 bytes) and generated at a size of 700x700
with error correction level M (~15%).
Whatever library or code you are using for the QR Code generation should allow you to pass these options.
The data should be read from bytes into a (byte)bitmap and if encoding to a string is required for reading into it with your library, ISO-8859-1
is recommended.
The QR Code can then be generated.
The output should be something like this:
For PRO design pattern QR codes the pixel data needs to be arranged into different chunks of pixels.
Imagine that the whole 64x64 image is split into quadrants, and then imagine that the bottom quadrants are split in half horizontally, which leaves you something like this:
🟦🟦🟨🟨
🟦🟦🟨🟨
🟥🟥🟪🟪
🟩🟩🟧🟧
Let's identify them as such:
- 🟦: Q1
- 🟨: Q2
- 🟥: Q3a
- 🟪: Q4a
- 🟩: Q3b
- 🟧: Q4b
They can be presented as a dictionary like below:
// Expressed as:
// { (AddrStart, AddrEnd), AddrDestination }
// Front Chunk (Q1)
{ ( 0x000, 0x200), 0x200 },
// Back Chunk (Q2)
{ ( 0x200, 0x400), 0x000 },
// Front Bottom Chunk (Q3b)
{ ( 0x600, 0x700), 0x600 },
// Back Bottom Chunk (Q4b)
{ ( 0x700, 0x800), 0x400 },
// Left Sleeve Chunk (Q3a)
{ ( 0x400, 0x500), 0x500 },
// Right Sleeve Chunk (Q4a)
{ ( 0x500, 0x600), 0x700 },
We need to move the chunks as follows:
- Q2 and Q1 need to swap position.
- Q3b needs to move to Q3a.
- Q3a needs to move to Q4b.
- Q4b needs to move to Q3b.
- Q4a is in it's correct position.
Our new chunk arrangement should be arranged as such:
🟨🟨🟦🟦
🟨🟨🟦🟦
🟩🟩🟪🟪
🟧🟧🟥🟥
Imagine that the whole 64x64 image is split into quadrants, which leaves you something like this:
🟦🟨
🟥🟪
Let's identify them as such:
- 🟦: Q1
- 🟨: Q2
- 🟥: Q3
- 🟪: Q4
They can be presented as a dictionary like below:
// Expressed as:
// { (AddrStart, AddrEnd), AddrDestination }
// Top Left Chunk (Q1)
{ ( 0x000, 0x200), 0x000 },
// Bottom Left Chunk (Q3)
{ ( 0x200, 0x400), 0x400 },
// Top Right Chunk (Q2)
{ ( 0x400, 0x600), 0x200 },
// Bottom Right Chunk (Q4)
{ ( 0x600, 0x800), 0x600 },
We need to move the chunks as follows:
- Q1 is in it's correct position.
- Q2 and Q3 need to swap position.
- Q4 is in it's correct position.
Our new chunk arrangement should be arranged as such:
🟦🟥
🟨🟪
After that, the whole data array needs to be split into 4 parts (540 bytes each) from 0x00
. This is used to generate 4 QR Codes using the structural append feature in QR Code.
Each QR Code needs to be a size of 400x400
and each one will require a sequence number, total number of symbols and parity value passed to it.
The parity value can be randomly generated and should be between 0 to 255.
Error correction level M (~15%) is required as above and each of the data parts should be read from bytes into a (byte)bitmap and if an encoding to a string is required for reading into it with your library, ISO-8859-1
is recommended.
The QR Code can then be generated.
The 4 QR Codes can then optionally be stitched into an 800x800
canvas to keep them stored together like below:
🟨🟩
🟦🟪
- 🟨: Sequence 1 QR Code
- 🟩: Sequence 2 QR Code
- 🟦: Sequence 3 QR Code
- 🟪: Sequence 4 QR Code
The output should be something like this: