1#[cfg(test)]
2mod tests {
3 use crate::*;
4 #[test]
5 fn packing() {
6 let keys = load_keys("key_retail.bin").unwrap(); let sample_signed : [u8; AMIIBO_SIZE] = std::fs::read("sample_signed.bin").unwrap().try_into().unwrap();
9 let sample_unsigned : [u8; AMIIBO_SIZE]= std::fs::read("sample2_unsigned.bin").unwrap().try_into().unwrap();
11
12 let packed_sample : [u8; AMIIBO_SIZE] = PlainAmiibo::pack(sample_unsigned.into(), &keys).unwrap().into();
13 std::fs::write("testpack.bin", packed_sample).unwrap();
14 assert_eq!(packed_sample, sample_signed);
15 }
16 #[test]
17 fn unpacking() {
18 let keys = load_keys("key_retail.bin").unwrap();
19 let sample_signed : [u8; AMIIBO_SIZE] = std::fs::read("sample_signed.bin").unwrap().try_into().unwrap();
20 let sample_unsigned : [u8; AMIIBO_SIZE] = std::fs::read("sample2_unsigned.bin").unwrap().try_into().unwrap();
21
22 let unpacked_sample : [u8; AMIIBO_SIZE] = PackedAmiibo::unpack(sample_signed.into(), &keys).unwrap().get_checked().expect("Invalid signature").into();
23 std::fs::write("testunpack.bin", unpacked_sample).unwrap();
24 assert_eq!(unpacked_sample, sample_unsigned);
25 }
26}
27
28const KEYGEN_SEED_SIZE: usize = 64;
29const DRBG_OUTPUT_SIZE: usize = 32;
30use hmac::Mac;
31use sha2::Sha256;
32use std::io;
33use std::io::{Cursor, Write};
34#[derive(Copy, Clone, Debug)]
36pub struct MasterKeys {
37 pub hmac_key: [u8; 16],
38 pub type_string: [u8; 14],
39 pub magic_bytes_size: u8,
40 pub magic_bytes: [u8; 16],
41 pub xor_pad: [u8; 32],
42}
43#[derive(Default,Copy, Clone,Debug)]
45struct DerivedKeys {
46 aes_key: [u8; 16],
47 aes_iv: [u8; 16],
48 hmac_key: [u8; 16],
49}
50#[derive(Debug, Copy, Clone)]
53pub struct PackedAmiibo {
54 amiibo: [u8; AMIIBO_SIZE]
55}
56#[derive(Debug, Copy, Clone)]
59pub struct PlainAmiibo {
60 amiibo: [u8; AMIIBO_SIZE]
61}
62impl PlainAmiibo {
63 pub fn amiibo_id(&self) -> [u8; 8] {
65 self.amiibo[0x1DC..=0x1E3].try_into().unwrap()
66 }
67 pub fn character_id(&self) -> [u8; 2] {
69 self.amiibo[0x1DC..=0x1DD].try_into().unwrap()
70 }
71 pub fn nickname(&self) -> String {
73 let mut name_buf : [u8; 20] = self.amiibo[0x38..=0x4B].try_into().unwrap();
74 for i in (0usize..20usize).step_by(2) {
75 let tmp = name_buf[i];
76 name_buf[i] = name_buf[i + 1];
77 name_buf[i + 1] = tmp;
78 }
79 let utf16_buf : [u16; 10] = name_buf.chunks(2).map(|x| u16::from_le_bytes(x.try_into().unwrap())).collect::<Vec<u16>>().try_into().unwrap();
80 String::from_utf16_lossy(&utf16_buf)
81 }
82 pub fn mii_name(&self) -> String {
84 let name_buf : [u16; 10] = self.amiibo[0x66..=0x79].chunks(2).map(|x| u16::from_le_bytes(x.try_into().unwrap())).collect::<Vec<u16>>().try_into().unwrap();
85 String::from_utf16_lossy(&name_buf)
86 }
87
88}
89impl From<[u8; AMIIBO_SIZE]> for PackedAmiibo {
90 fn from(amiibo: [u8; AMIIBO_SIZE]) -> Self {
91 PackedAmiibo { amiibo }
92 }
93}
94impl From<[u8; AMIIBO_SIZE]> for PlainAmiibo {
95 fn from(amiibo: [u8; AMIIBO_SIZE]) -> Self {
96 PlainAmiibo { amiibo }
97 }
98}
99impl From<PlainAmiibo> for [u8; AMIIBO_SIZE] {
100 fn from(thing: PlainAmiibo) -> [u8; AMIIBO_SIZE] {
101 thing.amiibo
102 }
103}
104impl From<PackedAmiibo> for [u8; AMIIBO_SIZE] {
105 fn from(thing: PackedAmiibo) -> [u8; AMIIBO_SIZE] {
106 thing.amiibo
107 }
108}
109
110impl TryFrom<&[u8]> for PackedAmiibo {
111 type Error = std::array::TryFromSliceError;
112 fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
113 let arr = slice.try_into()?;
114 Ok(PackedAmiibo { amiibo: arr })
115 }
116}
117
118impl TryFrom<&[u8]> for PlainAmiibo {
119 type Error = std::array::TryFromSliceError;
120 fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
121 let arr = slice.try_into()?;
122 Ok(PlainAmiibo { amiibo: arr })
123 }
124}
125
126impl PlainAmiibo {
127 pub fn pack(self, amiibo_keys: &AmiiboKeys) -> Result<PackedAmiibo, AmiitoolError> {
129 let plain : [u8; AMIIBO_SIZE] = self.into();
130 let mut cipher = [0; AMIIBO_SIZE];
131
132 let tag_keys = amiibo_keygen(&amiibo_keys.tag, plain)?;
134 let data_keys = amiibo_keygen(&amiibo_keys.data, plain)?;
135 cipher[len_range(HMAC_POS_TAG, DRBG_OUTPUT_SIZE)].copy_from_slice(&compute_hmac(tag_keys.hmac_key, &plain, 0x1d4, 0x34));
136 let mut data = Vec::new();
137 data.extend_from_slice(&plain[len_range(0x29, 0x18B)]);
139 data.extend_from_slice(&cipher[len_range(HMAC_POS_TAG,0x20)]); data.extend_from_slice(&plain[len_range(0x1D4, 0x34)]); cipher[len_range(HMAC_POS_DATA, DRBG_OUTPUT_SIZE)].copy_from_slice(&compute_hmac(data_keys.hmac_key, data.as_slice(), 0, data.len()));
142
143 amiibo_cipher(&data_keys, plain, &mut cipher);
145 let mut packed = amiibo_internal_to_tag(cipher);
146 memcpy(&mut packed, 0x208, &plain, 0x208, 0x14);
148 Ok(packed.into())
149 }
150 pub fn generate(amiibo_id: [u8; 8], tag_uid: &[u8]) -> Result<Self, AmiitoolError> {
154 if tag_uid.len() != 7 && tag_uid.len() != 9 {
155 Err(AmiitoolError { why: "Not a 7 or 9 byte tag uid".to_string() } )
156 } else {
157 if tag_uid[0] != 0x04 {
158 return Err(AmiitoolError { why: "Not a valid tag uid".to_string()});
159 }
160 let (small_uid, bcc1, uid) = match tag_uid.len() {
161 7 => {
162 let bcc0 = 0x88 ^ tag_uid[0] ^ tag_uid[1] ^ tag_uid[2];
163 let bcc1 = tag_uid[3] ^ tag_uid[4] ^ tag_uid[5] ^ tag_uid[6];
164 (
165 tag_uid.try_into().expect("Already checked size"),
166 bcc1,
167 [
168 tag_uid[0], tag_uid[1], tag_uid[2], bcc0, tag_uid[3], tag_uid[4], tag_uid[5],
169 tag_uid[6],
170 ]
171 )
172 }
173 9 => {
174 let small_uid = [
175 tag_uid[0], tag_uid[1], tag_uid[2], tag_uid[4], tag_uid[5], tag_uid[6], tag_uid[7]
176 ];
177 (small_uid, tag_uid[8], tag_uid[0..8].try_into().expect("Fixed slice length"))
178 }
179 _ => unreachable!()
180 };
181
182 let pw1 = 0xAA ^ small_uid[1] ^ small_uid[3];
183 let pw2 = 0x55 ^ small_uid[2] ^ small_uid[4];
184 let pw3 = 0xAA ^ small_uid[3] ^ small_uid[5];
185 let pw4 = 0x55 ^ small_uid[4] ^ small_uid[6];
186 let mut amiibo : [u8; AMIIBO_SIZE] = [0; AMIIBO_SIZE];
187 amiibo[len_range(0x1d4, 8)].copy_from_slice(&uid);
189 amiibo[len_range(0,8)].copy_from_slice(&[bcc1, 0x48, 0 ,0 , 0xF1, 0x10, 0xFF, 0xEE]);
190 amiibo[len_range(0x28, 4)].copy_from_slice(&[0xA5, 0,0,0]);
192 amiibo[0x20F] = 0x04;
194 amiibo[len_range(0x210, 4)].copy_from_slice(&[0x5F,0,0,0]);
196 amiibo[len_range(0x208, 4)].copy_from_slice(&[0x01, 0x00, 0x0F, 0xBD]);
198 amiibo[0x214] = pw1;
199 amiibo[0x215] = pw2;
200 amiibo[0x216] = pw3;
201 amiibo[0x217] = pw4;
202 amiibo[0x218] = 0x80;
203 amiibo[0x219] = 0x80;
204 let mut rng = rand::thread_rng();
206 rng.fill(&mut amiibo[len_range(0x1E8, 32)]);
207 amiibo[len_range(0x1DC, 8)].copy_from_slice(&amiibo_id);
210
211 Ok(amiibo.into())
212 }
213 }
214}
215impl PackedAmiibo {
216 pub fn unpack(self, amiibo_keys: &AmiiboKeys) -> Result<UnverifiedAmiibo, AmiitoolError> {
218 let tag : [u8; AMIIBO_SIZE] = self.into();
219 let intl = amiibo_tag_to_internal(tag);
221
222 let data_keys = amiibo_keygen(&amiibo_keys.data, intl)?;
224 let tag_keys = amiibo_keygen(&amiibo_keys.tag, intl)?;
225
226 let mut plain = null_cipher(&data_keys, intl);
228 let cur_plain = plain.clone();
229 plain[len_range(HMAC_POS_TAG, DRBG_OUTPUT_SIZE)].copy_from_slice(&compute_hmac(tag_keys.hmac_key, &cur_plain, 0x1d4, 0x34));
231
232 let cur_plain = plain.clone();
234 plain[len_range(HMAC_POS_DATA, DRBG_OUTPUT_SIZE)].copy_from_slice(&compute_hmac(data_keys.hmac_key, &cur_plain, 0x29, 0x1df));
235 memcpy(&mut plain, 0x208, &tag, 0x208, 0x14);
237 Ok(UnverifiedAmiibo {data: plain, intl} )
238 }
239 pub fn generate(amiibo_id: [u8; 8], tag_uid: &[u8], amiibo_keys: &AmiiboKeys) -> Result<Self, AmiitoolError> {
242 let plain = PlainAmiibo::generate(amiibo_id, tag_uid)?;
243 plain.pack(amiibo_keys)
244 }
245}
246#[derive(Clone, Debug)]
248pub struct AmiitoolError {
249 pub why : String
250}
251
252impl From<AmiitoolError> for io::Error {
253 fn from(err: AmiitoolError) -> Self {
254 io::Error::new(io::ErrorKind::Other, err.why)
255 }
256}
257impl From<io::Error> for AmiitoolError {
258 fn from(err: io::Error) -> Self {
259 AmiitoolError { why: format!("{}",err) }
260 }
261}
262impl std::fmt::Display for AmiitoolError {
263 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
264 write!(f, "Amiitool error: {}", self.why)
265 }
266}
267#[inline]
268fn len_range(start: usize, len : usize) -> std::ops::Range<usize> {
269 start..start+len
270}
271fn memcpy<T:Copy>(dest: &mut [T], dest_offset: usize, source: &[T], source_offset: usize, len : usize) {
272 dest[len_range(dest_offset, len)].copy_from_slice(&source[len_range(source_offset, len)])
273}
274fn keygen_prepare_seed(base_keys: &MasterKeys, base_seed: [u8; KEYGEN_SEED_SIZE]) -> Result<Vec<u8>, AmiitoolError> {
275 if base_keys.magic_bytes_size > 16 {
276 return Err(AmiitoolError { why : "magic byte size too big".to_string() } );
277 }
278
279 let mut cursor = Cursor::new(Vec::new());
280 let da_str : Vec<u8>= base_keys.type_string.iter().take_while(|x| **x != 0).copied().collect();
282 cursor.write(&da_str)?;
284 cursor.write(&[0])?;
287
288
289 let lead_size: usize = 16 - base_keys.magic_bytes_size as usize;
291 cursor.write(&base_seed[len_range(0, lead_size)])?;
293 cursor.write(&base_keys.magic_bytes[0..base_keys.magic_bytes_size as usize])?;
295 cursor.write(&base_seed[len_range(0x10,16)])?;
297 for i in 0..32 {
298 cursor.write(&[base_seed[i + 32] ^ base_keys.xor_pad[i]])?;
299 }
300 let vec = cursor.into_inner();
301 Ok(vec)
302}
303
304fn keygen_gen(base_keys: &MasterKeys, base_seed: [u8; KEYGEN_SEED_SIZE]) -> Result<DerivedKeys, AmiitoolError> {
305 let res = keygen_prepare_seed(base_keys, base_seed)?;
306 let bytes = drbg_gen_bytes(base_keys.hmac_key, &res);
307 Ok(bytes)
308}
309fn drbg_init(hmac_key: [u8; 16]) -> Hmac256 {
310 Hmac256::new_from_slice(&hmac_key).expect("fixed slice length")
311}
312fn drbg_step(hmac: &mut Hmac256, seed: &[u8], iteration: &mut u16) -> [u8; DRBG_OUTPUT_SIZE] {
313 let mut vec = Vec::new();
314 vec.extend_from_slice(&u16::to_be_bytes(*iteration));
315 vec.extend_from_slice(&seed);
316 *iteration += 1;
317 hmac.update(vec.as_slice());
318 hmac.finalize_reset().into_bytes().try_into().unwrap()
319}
320
321type Hmac256 = hmac::Hmac<Sha256>;
322fn drbg_gen_bytes<'a>(hmac_key: [u8; 16], seed: &[u8]) -> DerivedKeys {
323 let mut i = 0;
324 let mut out = [0u8; 48];
325 let mut hmac = drbg_init(hmac_key);
326 out[0..32].copy_from_slice(&drbg_step(&mut hmac, seed, &mut i));
327 out[32..48].copy_from_slice(&drbg_step(&mut hmac, seed, &mut i)[0..16]);
328 DerivedKeys {
329 aes_key : out[0..16].try_into().expect("Fixed slice size"),
330 aes_iv: out[16..32].try_into().expect("fixed slice size"),
331 hmac_key: out[32..48].try_into().expect("fixed slice size")
332 }
333}
334pub const AMIIBO_SIZE: usize = 540;
335fn amiibo_calc_seed(dump: [u8; AMIIBO_SIZE]) -> [u8; KEYGEN_SEED_SIZE] {
336 let mut key: [u8; KEYGEN_SEED_SIZE] = [0; KEYGEN_SEED_SIZE];
337 memcpy(&mut key, 0x00, &dump, 0x029, 0x02);
338 key[len_range(2, 0xE)].fill(0);
339 memcpy(&mut key, 0x10, &dump, 0x1D4, 0x08);
340 memcpy(&mut key, 0x18, &dump, 0x1D4, 0x08);
341 memcpy(&mut key, 0x20, &dump, 0x1E8, 0x20);
342 key
343}
344fn amiibo_keygen(master_keys: &MasterKeys, dump: [u8; AMIIBO_SIZE]) -> Result<DerivedKeys, AmiitoolError> {
345 let seed = amiibo_calc_seed(dump);
346 keygen_gen(master_keys, seed)
347}
348
349use aes::cipher::{KeyIvInit, StreamCipher};
350type Aes128Ctr = ctr::Ctr128BE<aes::Aes128>;
351fn amiibo_cipher(derived_keys: &DerivedKeys, input: [u8; AMIIBO_SIZE], out: &mut[u8; AMIIBO_SIZE]) -> () {
353 let mut cipher: Aes128Ctr =
354 Aes128Ctr::new(&derived_keys.aes_key.into(), &derived_keys.aes_iv.into());
355
356
357 cipher.apply_keystream_b2b(&input[len_range(0x2c, 0x188)], &mut out[len_range(0x2c, 0x188)]).unwrap();
358 memcpy(out, 0, &input, 0, 0x8);
359 memcpy(out, 0x28, &input, 0x28, 0x4);
361 memcpy(out, 0x1d4, &input, 0x1d4, 0x34);
363
364}
365fn null_cipher(derived_keys: &DerivedKeys, input: [u8; AMIIBO_SIZE]) -> [u8; AMIIBO_SIZE] {
366 let mut out = [0; AMIIBO_SIZE];
367 amiibo_cipher(derived_keys, input, &mut out);
368 out
369}
370fn amiibo_tag_to_internal(tag: [u8; AMIIBO_SIZE]) -> [u8; AMIIBO_SIZE] {
371 let mut out = [0; AMIIBO_SIZE];
372 memcpy(&mut out, 0x0, &tag, 0x8, 0x8);
374 memcpy(&mut out, 0x8, &tag, 0x80, 0x20);
376 memcpy(&mut out, 0x28, &tag, 0x10, 0x24);
378 memcpy(&mut out, 0x4c, &tag, 0xa0, 0x168);
380 memcpy(&mut out, 0x1b4, &tag, 0x34, 0x20);
382 memcpy(&mut out, 0x1d4, &tag, 0x0, 0x8);
384 memcpy(&mut out, 0x1dc, &tag, 0x54, 0x2c);
386 out.try_into().unwrap()
387}
388
389fn amiibo_internal_to_tag(internal: [u8; AMIIBO_SIZE]) -> [u8; AMIIBO_SIZE] {
390 let mut out = [0; AMIIBO_SIZE];
391 memcpy(&mut out, 0x008, &internal, 0x000, 0x008);
392 memcpy(&mut out, 0x080, &internal, 0x008, 0x020);
393 memcpy(&mut out, 0x010, &internal, 0x028, 0x024);
394 memcpy(&mut out, 0x0A0, &internal, 0x04C, 0x168);
395 memcpy(&mut out, 0x034, &internal, 0x1B4, 0x020);
396 memcpy(&mut out, 0x000, &internal, 0x1D4, 0x008);
397 memcpy(&mut out, 0x054, &internal, 0x1DC, 0x02C);
399 out
400}
401pub struct AmiiboKeys {
403 pub data: MasterKeys,
404 pub tag: MasterKeys,
405}
406impl AmiiboKeys {
407 pub fn load_keys(key: [u8; 160]) -> Result<Self, AmiitoolError> {
409 let data = MasterKeys::load_key(key[0..80].try_into().unwrap())?;
410 let tag = MasterKeys::load_key(key[80..160].try_into().unwrap())?;
411 Ok(AmiiboKeys {data, tag})
412 }
413}
414impl MasterKeys {
415 pub fn load_key(key: [u8; 80]) -> Result<Self, AmiitoolError> {
417 let res = MasterKeys {
418 hmac_key: key[0..16].try_into().expect("Fixed slice size"),
419 type_string: key[16..30].try_into().expect("Fixed slice size"),
420 magic_bytes_size: key[31], magic_bytes: key[32..48].try_into().expect("Fixed slice size"),
422 xor_pad: key[48..80].try_into().expect("Fixed slice size")
423 };
424 if res.magic_bytes_size > 16 {
425 Err(AmiitoolError { why: "magic bytes too big".to_string() })
426 } else {
427 Ok(res)
428 }
429 }
430}
431const HMAC_POS_DATA: usize = 0x8;
432const HMAC_POS_TAG: usize = 0x1b4;
433#[derive(Clone, Debug)]
435pub struct UnverifiedAmiibo {
436 data : [u8; AMIIBO_SIZE],
437 intl : [u8; AMIIBO_SIZE]
438}
439
440impl UnverifiedAmiibo {
441 pub fn get_checked(self) -> Result<PlainAmiibo, AmiitoolError> {
443 if self.is_valid() {
444 Ok(self.data.into())
445 } else {
446 Err(AmiitoolError { why : "Invalid Signature".to_string() })
447 }
448 }
449 pub fn is_valid(&self) -> bool {
451 self.data[HMAC_POS_DATA..HMAC_POS_DATA + 32] == self.intl[HMAC_POS_DATA..HMAC_POS_DATA + 32]
452 && self.data[HMAC_POS_TAG..HMAC_POS_TAG + 32] == self.intl[HMAC_POS_TAG..HMAC_POS_TAG + 32]
453 }
454 pub fn get_unchecked(self) -> PlainAmiibo {
456 self.data.into()
457 }
458}
459#[deprecated]
461pub fn amiibo_unpack(amiibo_keys: &AmiiboKeys, amiibo: PackedAmiibo) -> Result<UnverifiedAmiibo, AmiitoolError> {
463 amiibo.unpack(amiibo_keys)
464}
465#[deprecated]
467pub fn amiibo_pack(amiibo_keys: &AmiiboKeys, plain_amiibo: PlainAmiibo) -> Result<PackedAmiibo, AmiitoolError> {
469 plain_amiibo.pack(amiibo_keys)
470}
471use std::io::{BufRead, BufReader, Read};
472pub fn load_keys(path: &str) -> Result<AmiiboKeys, AmiitoolError> {
474 let file = std::fs::File::open(path)?;
475 let mut buf = BufReader::new(file);
476 let mut out = [0; 160];
477 buf.read_exact(&mut out)?;
478 let in_buf = buf.fill_buf()?;
479 if !in_buf.is_empty() {
480 Err(AmiitoolError {
481 why : "not valid key file".to_string(),
482 })
483 } else {
484 AmiiboKeys::load_keys(out)
485 }
486}
487#[deprecated]
489pub fn read_master_key(key: [u8; 80]) -> Result<MasterKeys, AmiitoolError> {
491 MasterKeys::load_key(key)
492}
493
494
495use rand::Rng;
496
497#[deprecated]
498pub fn gen_amiibo_raw(amiibo_id: [u8; 8], tag_uid: &[u8]) -> Result<PlainAmiibo, AmiitoolError> {
500 PlainAmiibo::generate(amiibo_id, tag_uid)
501}
502
503#[deprecated]
504pub fn gen_amiibo(key: &AmiiboKeys, amiibo_id: [u8; 8], tag_uid: &[u8]) -> Result<PackedAmiibo, AmiitoolError> {
506 PackedAmiibo::generate(amiibo_id, tag_uid, key)
507}
508
509fn compute_hmac(key: [u8; 16], input: &[u8], input_offset: usize, input_len: usize) -> [u8; DRBG_OUTPUT_SIZE] {
510 let mut hmac = Hmac256::new_from_slice(&key).expect("fixed length slice");
511 hmac.update(&input[len_range(input_offset, input_len)]);
512 hmac.finalize().into_bytes().try_into().unwrap()
513
514}
515#[deprecated]
517pub fn read_amiibo_keys(key: [u8; 160]) -> Result<AmiiboKeys, AmiitoolError> {
519 AmiiboKeys::load_keys(key)
520}
521