amiitool_rs/
lib.rs

1#[cfg(test)]
2mod tests {
3    use crate::*;
4    #[test]
5    fn packing() {
6        let keys = load_keys("key_retail.bin").unwrap(); // commiting crimes time :grin:
7        // this is emitted from amiitool, which is known to produce good bins
8        let sample_signed  : [u8; AMIIBO_SIZE] = std::fs::read("sample_signed.bin").unwrap().try_into().unwrap(); 
9        // this is an unencrpyted file
10        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/// The Master Keys for amiibo. 
35#[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/// Keys derived from master keys.
44#[derive(Default,Copy, Clone,Debug)]
45struct DerivedKeys {
46    aes_key: [u8; 16],
47    aes_iv: [u8; 16],
48    hmac_key: [u8; 16],
49}
50/// Wrapper type for raw data to ensure valid data. Can be converted to raw type with 
51/// [std::convert::Into]
52#[derive(Debug, Copy, Clone)]
53pub struct PackedAmiibo {
54    amiibo: [u8; AMIIBO_SIZE]
55}
56/// Wrapper Type for raw data to ensure valid data. Can be converted to raw type with
57/// [std::convert::Into]
58#[derive(Debug, Copy, Clone)]
59pub struct PlainAmiibo {
60    amiibo: [u8; AMIIBO_SIZE]
61}
62impl PlainAmiibo {
63    /// Amiibo ID
64    pub fn amiibo_id(&self) -> [u8; 8] {
65        self.amiibo[0x1DC..=0x1E3].try_into().unwrap()
66    }
67    /// Character ID; Part of Amiibo ID
68    pub fn character_id(&self) -> [u8; 2] {
69        self.amiibo[0x1DC..=0x1DD].try_into().unwrap()
70    }
71    /// Amiibo Nickname
72    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    /// Mii name
83    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    /// Packs plain amiibo to Packed Amiibo. 
128    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        // generate keys
133        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
138        data.extend_from_slice(&plain[len_range(0x29, 0x18B)]);
139        data.extend_from_slice(&cipher[len_range(HMAC_POS_TAG,0x20)]); // Tag HMAC
140        data.extend_from_slice(&plain[len_range(0x1D4, 0x34)]); // Unknown
141        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        // encrypt
144        amiibo_cipher(&data_keys, plain, &mut cipher); 
145        let mut packed = amiibo_internal_to_tag(cipher);
146        // why is this here :sob: 
147        memcpy(&mut packed, 0x208, &plain, 0x208, 0x14);
148        Ok(packed.into())
149    }
150    /// Generate plain amiibo from Tag UID and Amiibo ID 
151    /// # Error conditions
152    /// Tag UID must be 7 or 9 bytes in length, and must begin with 0x04. 
153    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            // set UID
188            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            // 0xA5 byte, write counter, unknown
191            amiibo[len_range(0x28, 4)].copy_from_slice(&[0xA5, 0,0,0]); 
192            // CFG 0 
193            amiibo[0x20F] = 0x04;
194            // CFG 1
195            amiibo[len_range(0x210, 4)].copy_from_slice(&[0x5F,0,0,0]);
196            // dynamic lock bits and RFUI
197            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            // keygen salt
205            let mut rng = rand::thread_rng();
206            rng.fill(&mut amiibo[len_range(0x1E8, 32)]);
207            // TODO: this depends on input being big endian 
208            // amiibo id 
209            amiibo[len_range(0x1DC, 8)].copy_from_slice(&amiibo_id);
210
211            Ok(amiibo.into())    
212        }
213    }
214}
215impl PackedAmiibo {
216    /// Unpacks amiibo.
217    pub fn unpack(self, amiibo_keys: &AmiiboKeys) -> Result<UnverifiedAmiibo, AmiitoolError> {
218        let tag : [u8; AMIIBO_SIZE] = self.into();
219        // convert format
220        let intl = amiibo_tag_to_internal(tag);
221
222        // Generate keys
223        let data_keys = amiibo_keygen(&amiibo_keys.data, intl)?;
224        let tag_keys = amiibo_keygen(&amiibo_keys.tag, intl)?;
225
226        // decrypt
227        let mut plain = null_cipher(&data_keys, intl);
228        let cur_plain = plain.clone();
229        // Regenerate tag HMAC. Order matters, data HMAC depends on tag HMAC.
230        plain[len_range(HMAC_POS_TAG, DRBG_OUTPUT_SIZE)].copy_from_slice(&compute_hmac(tag_keys.hmac_key, &cur_plain, 0x1d4, 0x34));
231
232        // Regenerate data HMAC.
233        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        // idk what it does so i get rid of it
236        memcpy(&mut plain, 0x208, &tag, 0x208, 0x14);
237        Ok(UnverifiedAmiibo {data: plain, intl} )
238    }
239    /// Generates PackedAmiibo given Amiibo ID and Tag UID. See [PlainAmiibo::generate] for
240    /// guidelines. 
241    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/// AmiitoolError. Can be converted to [std::io::Error] with [std::convert::Into].
247#[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    // this is meant to get all things
281    let da_str : Vec<u8>= base_keys.type_string.iter().take_while(|x| **x != 0).copied().collect();
282    // 1: Copy whole type string
283    cursor.write(&da_str)?;
284    // this single line fixes the entire lib. ffk;afoih
285    // putting my thinking cap on this is the `\0` at the end of a C string 
286    cursor.write(&[0])?;
287
288    
289    // 2 : append (16 - magic_bytes_size) from the base seed
290    let lead_size: usize = 16 - base_keys.magic_bytes_size as usize;
291    // again, prints the same as amiitool
292    cursor.write(&base_seed[len_range(0, lead_size)])?;
293    // 3: append all bytes from magic bytes
294    cursor.write(&base_keys.magic_bytes[0..base_keys.magic_bytes_size as usize])?;
295    // 4: Append bytes 0x10-0x1F
296    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>;
351// slightly wrong output on not real inputs
352fn 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    // data sig NOT copied
360    memcpy(out, 0x28, &input, 0x28, 0x4);
361    // tag sig NOT copied
362    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    // BCC1, internal, static lock, CC
373    memcpy(&mut out, 0x0, &tag, 0x8, 0x8);
374    // unfixed hash
375    memcpy(&mut out, 0x8, &tag, 0x80, 0x20);
376    // pages 4-12 inclusive
377    memcpy(&mut out, 0x28, &tag, 0x10, 0x24);
378    // pages 40-129 inclusive 
379    memcpy(&mut out, 0x4c, &tag, 0xa0, 0x168);
380    // pages 13 to 20 inclusive 
381    memcpy(&mut out, 0x1b4, &tag, 0x34, 0x20);
382    // UID 
383    memcpy(&mut out, 0x1d4, &tag, 0x0, 0x8);
384    // amiibo id
385    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    // amiibo id ? 
398    memcpy(&mut out, 0x054, &internal, 0x1DC, 0x02C);
399    out
400}
401/// AmiiboKeys from file. 
402pub struct AmiiboKeys {
403    pub data: MasterKeys,
404    pub tag: MasterKeys,
405}
406impl AmiiboKeys {
407    /// Loads key given bytes. 
408    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    /// Loads key given bytes. 
416    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], // 31 is intentional, i'm skipping over an unused field
421            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/// An amiibo that can possibly have an invalid signature. 
434#[derive(Clone, Debug)]
435pub struct UnverifiedAmiibo {
436    data : [u8; AMIIBO_SIZE],
437    intl : [u8; AMIIBO_SIZE]
438}
439
440impl UnverifiedAmiibo {
441    /// Verifies signature. Returns an error if signature is invalid. 
442    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    /// Returns true if signature is valid. 
450    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    /// Returns internal data, without verifying signature. 
455    pub fn get_unchecked(self) -> PlainAmiibo {
456        self.data.into()
457    }
458}
459// legacy function
460#[deprecated]
461/// Use [PackedAmiibo::unpack] instead
462pub fn amiibo_unpack(amiibo_keys: &AmiiboKeys, amiibo: PackedAmiibo) -> Result<UnverifiedAmiibo, AmiitoolError> {
463    amiibo.unpack(amiibo_keys)
464}
465// legacy function
466#[deprecated]
467/// Use [PlainAmiibo::pack] instead
468pub 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};
472/// Load keys from path. Prefer use of [std::fs::File] and [AmiiboKeys::load_keys] instead. 
473pub 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// legacy function
488#[deprecated]
489/// Use [MasterKeys::load_key] instead
490pub fn read_master_key(key: [u8; 80]) -> Result<MasterKeys, AmiitoolError> {
491    MasterKeys::load_key(key)
492}
493
494
495use rand::Rng;
496
497#[deprecated]
498/// Use [PlainAmiibo::generate] instead
499pub 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]
504/// Use [PackedAmiibo::generate] instead
505pub 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// legacy function 
516#[deprecated]
517/// Use [AmiiboKeys::load_keys] instead
518pub fn read_amiibo_keys(key: [u8; 160]) -> Result<AmiiboKeys, AmiitoolError> {
519    AmiiboKeys::load_keys(key)
520}
521/*
522fn printhex(data: &[u8]) {
523    for i in 0..data.len() {
524        if (i % 16) > 0 {
525            print!(" ");
526        }
527        print!("{:02X}", data[i]);
528        if (i % 16) == 15 {
529            print!("\n");
530        }
531    }
532    print!("\n");
533    if (data.len() % 16) != 0 {
534        print!("\n");
535    } 
536}
537
538*/