1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#[macro_use]
extern crate nom;
#[macro_use]
extern crate itertools;
extern crate crc;

mod internal;
mod parser;
mod writer;

use serde::{Serialize, Deserialize};

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Nus3audioFile {
    pub files: Vec<AudioFile>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
#[derive(binwrite::BinWrite)]
pub struct AudioFile {
    #[binwrite(ignore)]
    pub id: u32,
    #[binwrite(ignore)]
    pub name: String,
    #[binwrite(align_after(0x10))]
    pub data: Vec<u8>,
}

impl Nus3audioFile {
    pub fn new() -> Self {
        Nus3audioFile { files: vec![] }
    }

    pub fn from_bytes(data: &[u8]) -> Nus3audioFile {
        parser::take_file(&data[..])
            .expect("Failed to parse file")
            .1
    }

    pub fn try_from_bytes(data: &[u8]) -> Option<Nus3audioFile> {
        parser::take_file(&data[..])
            .map(|(_, file)| file)
            .ok()
    }

    pub fn open<P: AsRef<std::path::Path>>(path: P) -> Result<Nus3audioFile, std::io::Error> {
        Ok(Nus3audioFile::from_bytes(&std::fs::read(path)?[..]))
    }
}

impl AudioFile {
    pub fn from_id(id: u32) -> Self {
        AudioFile {
            data: vec![],
            name: String::new(),
            id,
        }
    }

    pub fn filename(&self) -> String {
        self.name.clone()
            + if self.data.len() >= 4 {
                match &self.data[..4] {
                    b"OPUS" => ".lopus",
                    b"IDSP" => ".idsp",
                    _ => ".bin",
                }
            } else {
                ".bin"
            }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    static TEST_FILE: &[u8] = b"NUS3x\x00\x00\x00AUDIINDX\x04\x00\x00\x00\x01\x00\x00\x00TNID\x04\x00\x00\x00E\x00\x00\x00NMOF\x04\x00\x00\x00H\x00\x00\x00ADOF\x08\x00\x00\x00p\x00\x00\x00\x05\x00\x00\x00TNNM\n\x00\x00\x00test_name\x00JUNK\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00PACK\x10\x00\x00\x00test\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
    static REPEAT_TEST_FILE: &[u8] = b"NUS3\x98\x00\x00\x00AUDIINDX\x04\x00\x00\x00\x02\x00\x00\x00TNID\x08\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00NMOF\x08\x00\x00\x00X\x00\x00\x00f\x00\x00\x00ADOF\x10\x00\x00\x00\x90\x00\x00\x00\x07\x00\x00\x00\x90\x00\x00\x00\x07\x00\x00\x00TNNM\x1c\x00\x00\x00repeat_test_1\x00repeat_test_2\x00JUNK\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00PACK\x10\x00\x00\x00repeat\n\x00\x00\x00\x00\x00\x00\x00\x00\x00";

    #[test]
    fn attempt_parse_test_file() {
        let nus_file = Nus3audioFile::from_bytes(&TEST_FILE[..]);
        assert_eq!(nus_file.files.len(), 1);
        assert_eq!(nus_file.files[0].name, "test_name");
        assert_eq!(nus_file.files[0].data, b"test\n");
        assert_eq!(nus_file.files[0].id, 69);
    }

    #[test]
    fn attempt_parse_repeat_test_file() {
        let nus_file = Nus3audioFile::from_bytes(&REPEAT_TEST_FILE[..]);
        assert_eq!(nus_file.files.len(), 2);
        assert_eq!(nus_file.files[0].name, "repeat_test_1");
        assert_eq!(nus_file.files[0].data, b"repeat\n");
        assert_eq!(nus_file.files[0].id, 0);
        assert_eq!(nus_file.files.len(), 2);
        assert_eq!(nus_file.files[1].name, "repeat_test_2");
        assert_eq!(nus_file.files[1].data, b"repeat\n");
        assert_eq!(nus_file.files[1].id, 1);
    }

    #[test]
    fn test_write_file() {
        let nus_file = Nus3audioFile::from_bytes(&REPEAT_TEST_FILE[..]);
        let mut nus_write_file: Vec<u8> = Vec::with_capacity(REPEAT_TEST_FILE.len());
        nus_file.write(&mut nus_write_file);
        assert_eq!(REPEAT_TEST_FILE, &nus_write_file[..]);
    }
}