Created
October 18, 2022 02:47
-
-
Save rob-brown/fbff18427994325aeadd32af3afdc11c to your computer and use it in GitHub Desktop.
HexFiend Amiibo Binary Template
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
little_endian | |
# HexFiend Reference: https://github.com/HexFiend/HexFiend/blob/master/templates/Reference.md | |
proc parse_system {} { | |
goto 0 | |
section -collapsed "System" { | |
hex 1 "BCC1 (UID3 ^ UID4 ^ UID5 ^ UID6)" | |
hex 1 "Internal (Always 0x48)" | |
hex 2 "Static Lock (Always 0x0FE0)" | |
hex 4 "Capability Container (CC)" | |
hex 32 "Unfixed Hash (Data Hash)" | |
hex 1 "Always 0xA5" | |
uint16 "Amiibo Write Counter" | |
hex 1 "?" | |
section "Settings" { | |
hex 1 "Flags" | |
hex 1 "Country Code ID" | |
uint16 "CRC Counter" | |
uint16 "Init Date" | |
uint16 "Write Date" | |
hex 4 "CRC" | |
big_endian | |
utf16 20 "Nickname" | |
little_endian | |
section "Owner Mii" { | |
parse_mii | |
} | |
hex 8 "Program ID" | |
uint16 "Config Write Counter" | |
hex 4 "App ID" | |
hex 2 "?" | |
hex 32 "Hash" | |
} | |
} | |
} | |
proc parse_row {n} { | |
set header [format "Row %d" $n] | |
section -collapsed $header { | |
uint16 -hex "Column 0" | |
uint16 -hex "Column 1" | |
uint16 -hex "Column 2" | |
uint16 -hex "Column 3" | |
uint16 -hex "Column 4" | |
uint16 -hex "Column 5" | |
uint16 -hex "Column 6" | |
uint16 -hex "Column 7" | |
} | |
} | |
proc parse_equipment {} { | |
section "Equipment" { | |
big_endian | |
set bytes [uint16 -hex "Equipment"] | |
little_endian | |
move 2 | |
if {[expr $bytes & 0x1]} { | |
entry "Sword?" "True" | |
} else { | |
entry "Sword?" "False" | |
} | |
if {[expr $bytes & 0x2]} { | |
entry "Magic Powder?" "True" | |
} else { | |
entry "Magic Powder?" "False" | |
} | |
if {[expr $bytes & 0x4]} { | |
entry "Roc's Feather?" "True" | |
} else { | |
entry "Roc's Feather?" "False" | |
} | |
if {[expr $bytes & 0x8]} { | |
entry "Power Bracelet?" "True" | |
} else { | |
entry "Power Bracelet?" "False" | |
} | |
if {[expr $bytes & 0x10]} { | |
entry "Ocarina" "True" | |
} else { | |
entry "Ocarina" "False" | |
} | |
if {[expr $bytes & 0x20]} { | |
entry "Bottle From Ghost" "True" | |
} else { | |
entry "Bottle From Ghost" "False" | |
} | |
if {[expr $bytes & 0x40]} { | |
entry "Bottle From Fishing" "True" | |
} else { | |
entry "Bottle From Fishing" "False" | |
} | |
if {[expr $bytes & 0x80]} { | |
entry "Bottle From Dampé" "True" | |
} else { | |
entry "Bottle From Dampé" "False" | |
} | |
if {[expr $bytes & 0x100]} { | |
entry "Pegasus Boots" "True" | |
} else { | |
entry "Pegasus Boots" "False" | |
} | |
if {[expr $bytes & 0x200]} { | |
entry "Shield" "True" | |
} else { | |
entry "Shield" "False" | |
} | |
if {[expr $bytes & 0x400]} { | |
entry "Bomb" "True" | |
} else { | |
entry "Bomb" "False" | |
} | |
if {[expr $bytes & 0x800]} { | |
entry "Bow" "True" | |
} else { | |
entry "Bow" "False" | |
} | |
if {[expr $bytes & 0x1000]} { | |
entry "Hookshot" "True" | |
} else { | |
entry "Hookshot" "False" | |
} | |
if {[expr $bytes & 0x2000]} { | |
entry "Boomerang" "True" | |
} else { | |
entry "Boomerang" "False" | |
} | |
if {[expr $bytes & 0x4000]} { | |
entry "Magic Rod" "True" | |
} else { | |
entry "Magic Rod" "False" | |
} | |
if {[expr $bytes & 0x8000]} { | |
entry "Shovel" "True" | |
} else { | |
entry "Shovel" "False" | |
} | |
} | |
} | |
proc parse_enhancements {} { | |
section "Enhancements" { | |
big_endian | |
set bytes [uint16 -hex "Enhancements"] | |
little_endian | |
move 2 | |
if {[expr $bytes & 0x1]} { | |
entry "Fairy in Bottle 1" "True" | |
} else { | |
entry "Fairy in Bottle 1" "False" | |
} | |
if {[expr $bytes & 0x2]} { | |
entry "Fairy in Bottle 2" "True" | |
} else { | |
entry "Fairy in Bottle 2" "False" | |
} | |
if {[expr $bytes & 0x4]} { | |
entry "Fairy in Bottle 3" "True" | |
} else { | |
entry "Fairy in Bottle 3" "False" | |
} | |
if {[expr $bytes & 0x8]} { | |
entry "Secret Medicine" "True" | |
} else { | |
entry "Secret Medicine" "False" | |
} | |
if {[expr $bytes & 0x10]} { | |
entry "Flippers" "True" | |
} else { | |
entry "Flippers" "False" | |
} | |
if {[expr $bytes & 0x20]} { | |
entry "Ballad of the Wind Fish" "True" | |
} else { | |
entry "Ballad of the Wind Fish" "False" | |
} | |
if {[expr $bytes & 0x40]} { | |
entry "Manbo's Mambo" "True" | |
} else { | |
entry "Manbo's Mambo" "False" | |
} | |
if {[expr $bytes & 0x80]} { | |
entry "Frog's Song of Soul" "True" | |
} else { | |
entry "Frog's Song of Soul" "False" | |
} | |
if {[expr $bytes & 0x100]} { | |
entry "Koholint Sword" "True" | |
} else { | |
entry "Koholint Sword" "False" | |
} | |
if {[expr $bytes & 0x200]} { | |
entry "Mirror Shield" "True" | |
} else { | |
entry "Mirror Shield" "False" | |
} | |
if {[expr $bytes & 0x400]} { | |
entry "Powerful Bracelet" "True" | |
} else { | |
entry "Powerful Bracelet" "False" | |
} | |
if {[expr $bytes & 0x800]} { | |
entry "Double Magic Dust" "True" | |
} else { | |
entry "Double Magic Dust" "False" | |
} | |
if {[expr $bytes & 0x1000]} { | |
entry "Double Bombs" "True" | |
} else { | |
entry "Double Bombs" "False" | |
} | |
if {[expr $bytes & 0x2000]} { | |
entry "Double Arrows" "True" | |
} else { | |
entry "Double Arrows" "False" | |
} | |
if {[expr $bytes & 0x4000]} { | |
entry "Red Mail" "True" | |
} else { | |
entry "Red Mail" "False" | |
} | |
if {[expr $bytes & 0x8000]} { | |
entry "Blue Mail" "True" | |
} else { | |
entry "Blue Mail" "False" | |
} | |
} | |
} | |
proc parse_dungeon_type {} { | |
# Unknown, always 0x03 | |
move 1 | |
set byte [uint8] | |
switch $byte { | |
0 { entry "Dungeon Type" "Challenge 1-1" 4 220 } | |
1 { entry "Dungeon Type" "Challenge 1-2" 4 220 } | |
2 { entry "Dungeon Type" "Challenge 1-3" 4 220 } | |
3 { entry "Dungeon Type" "Challenge 1-4" 4 220 } | |
4 { entry "Dungeon Type" "Challenge 2-1" 4 220 } | |
5 { entry "Dungeon Type" "Challenge 2-2" 4 220 } | |
6 { entry "Dungeon Type" "Challenge 2-3" 4 220 } | |
7 { entry "Dungeon Type" "Challenge 2-4" 4 220 } | |
8 { entry "Dungeon Type" "Challenge 3-1" 4 220 } | |
9 { entry "Dungeon Type" "Challenge 3-2" 4 220 } | |
10 { entry "Dungeon Type" "Challenge 3-3" 4 220 } | |
11 { entry "Dungeon Type" "Challenge 3-4" 4 220 } | |
12 { entry "Dungeon Type" "Challenge 4-1" 4 220 } | |
13 { entry "Dungeon Type" "Challenge 4-2" 4 220 } | |
14 { entry "Dungeon Type" "Challenge 4-3" 4 220 } | |
15 { entry "Dungeon Type" "Challenge 4-4" 4 220 } | |
16 { entry "Dungeon Type" "Challenge 4-5" 4 220 } | |
17 { entry "Dungeon Type" "Challenge 4-6" 4 220 } | |
18 { entry "Dungeon Type" "Challenge 4-7" 4 220 } | |
19 { entry "Dungeon Type" "Challenge 4-8" 4 220 } | |
20 { entry "Dungeon Type" "Challenge 4-9" 4 220 } | |
21 { entry "Dungeon Type" "Challenge 4-10" 4 220 } | |
22 { entry "Dungeon Type" "Challenge 4-11" 4 220 } | |
23 { entry "Dungeon Type" "Challenge 4-12" 4 220 } | |
48 { entry "Dungeon Type" "Free" 4 220 } | |
default { entry "Dungeon Type" "Invalid" 4 220 } | |
} | |
# Unknown, always 0x0100 | |
move 2 | |
} | |
proc parse_time {} { | |
set centiseconds [uint32] | |
set minutes [expr $centiseconds / 6000] | |
set seconds [expr $centiseconds % 6000 / 100] | |
set remainder [expr $centiseconds % 100] | |
set time [format "%02.0f:%02.0f.%02.0f" $minutes $seconds $remainder] | |
entry "Best Time" $time 4 420 | |
} | |
proc parse_link_awakening_app {} { | |
goto 0xDC | |
section "App" { | |
parse_dungeon_type | |
hex 4 "Unknown" | |
section -collapsed "Chambers" { | |
for {set n 0} {$n < 8} {incr n} { | |
parse_row $n | |
} | |
} | |
# Names can only be 10 characters long, but there | |
# are 32 bytes reserved. Uses UTF-8. It's possible to | |
# overflow if all 10 characters are 4-bytes each. | |
# The Switch keyboard probably doesn't use any | |
# characters that are more than 2-bytes. | |
str 32 "utf8" "Owner Name" | |
str 32 "utf8" "Best Record Name" | |
parse_time | |
parse_equipment | |
parse_enhancements | |
hex 4 "CRC" | |
} | |
} | |
proc parse_amiibo_number {} { | |
section "Amiibo Number" { | |
hex 2 "Character #" | |
hex 1 "Variation" | |
set form [uint8] | |
switch $form { | |
0 { | |
entry "Form" "Figurine (0)" 1 0x1DF | |
} | |
1 { | |
entry "Form" "Card (1)" 1 0x1DF | |
} | |
2 { | |
entry "Form" "Yarn (2)" 1 0x1DF | |
} | |
3 { | |
entry "Form" "Band (3)" 1 0x1DF | |
} | |
default { | |
entry "Form" "Unknown" 1 0x1DF | |
} | |
} | |
hex 2 "Amiibo #" | |
hex 1 "Set" | |
hex 1 "0x02" | |
} | |
} | |
proc parse_remainder {} { | |
goto 0x1B4 | |
section -collapsed "System" { | |
hex 32 "Locked Hash (Tag Hash)" | |
hex 1 "UID0 (Always 0x04)" | |
hex 1 "UID1" | |
hex 1 "UID2" | |
hex 1 "BCC0 (UID0 ^ UID1 ^ UID2 ^ 0x88)" | |
hex 1 "UID3" | |
hex 1 "UID4" | |
hex 1 "UID5" | |
hex 1 "UID6" | |
parse_amiibo_number | |
hex 36 "Keygen Salt" | |
hex 3 "Dynamic Lock (Always 0x01000F)" | |
hex 1 "RFUI (Always 0xBD)" | |
hex 4 "CFG0 (Always 0x00000004)" | |
hex 4 "CFG1 (Always 0x5F000000)" | |
hex 4 "PWD" | |
hex 2 "PACK" | |
hex 2 "RFUI" | |
} | |
} | |
proc parse_mii {} { | |
# See https://www.3dbrew.org/wiki/Mii#Checksum | |
set start [pos] | |
hex 1 "0x3" | |
set byte [uint8] | |
set allow_copying [expr ($byte & 0x1)] | |
set profanity_flag [expr ($byte & 0x2)] | |
set region_lock [expr ($byte & 0xC)] | |
set character_set [expr ($byte & 0x30)] | |
entry "Allow Copying" $allow_copying 1 [expr $start + 1] | |
entry "Profanity Flag" $profanity_flag 1 [expr $start + 1] | |
entry "Region Lock" $region_lock 1 [expr $start + 1] | |
entry "Character Set" $character_set 1 [expr $start + 1] | |
hex 1 "Mii position" | |
set version [expr [uint8] >> 4] | |
entry "Version" $version 1 [expr $start + 3] | |
hex 8 "System ID" | |
hex 4 "Mii ID" | |
hex 6 "Creator's MAC" | |
hex 2 "Padding" | |
set bytes [uint16] | |
if {[expr $bytes & 0x1] == 0} { | |
entry "Gender" "Male" 2 [expr $start + 0x18] | |
} else { | |
entry "Gender" "Female" 2 [expr $start + 0x18] | |
} | |
entry "Birthday Month" [expr ($bytes & 0x1E) >> 1] 2 [expr $start + 0x18] | |
entry "Birthday Day" [expr ($bytes & 0x3E0) >> 5] 2 [expr $start + 0x18] | |
entry "Favorite Color" [expr ($bytes & 0x3C00) >> 10] 2 [expr $start + 0x18] | |
entry "Favorite Mii" [expr ($bytes & 0x4000) >> 14] 2 [expr $start + 0x18] | |
utf16 20 "Amiibo Name" | |
hex 2 "Width and Height" | |
hex 1 "Sharing, face shape, and skin color" | |
hex 1 "Wrinkles and makeup" | |
hex 1 "Hair style" | |
hex 1 "Hair color and flip hair" | |
hex 4 "Eyes" | |
hex 4 "Eyebrows" | |
hex 2 "Nose" | |
hex 2 "Mouse" | |
hex 2 "Mouse and mustache" | |
hex 2 "Beard and mustache" | |
hex 2 "Glasses" | |
hex 2 "Mole" | |
utf16 20 "Author Name" | |
hex 2 "0x0000" | |
hex 2 "Mii Checksum" | |
} | |
parse_system | |
parse_link_awakening_app | |
parse_remainder |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment