vee_parse/
lib.rs

1//! Library for parsing and converting Mii character data.
2//!
3//! # Parsing
4//!
5//! There are, roughly, two kinds of Mii data type.
6//! - `CharInfo`: an uncompressed format.
7//! - `StoreData`: a packed format. This is space minimized for transit (e.g. on the flash memory of an NFC toy.)
8//!   - `CharData`: `StoreData` without the checksum footer.
9//! - _... there are more that this library does not implement [^morefmt]._
10//!
11//! [^morefmt]: Extra formats are used in Mii databases, and may need to be added if the library comes to require support for databases.
12//!
13//! Supported by this library:
14//!
15//! | ..          | Ntr[^gen1]           | Rvl[^gen1]       | Ctr/Cafe    | Nx             | WebStudio |
16//! |-------------|----------------------|------------------|-------------|----------------|-----------|
17//! | `CharInfo`  | ❌                   | ❌        | ❌                           | [`.charinfo`](NxCharInfo) | .. |
18//! | `StoreData` |  [`.nsd`](NtrStoreData) | [`.rsd`](RvlStoreData) | [`.ffsd`](CtrStoreData)[^ff]   | ❌ | .. |
19//! | `CoreData`  | [`.ncd`](NtrCharData) | [`.rcd`](RvlCharData) | ❌ | ❌ | .. |
20//! | In-memory   | .. | .. | .. | .. | 🏗️[^mnms]  |
21//! [^gen1]: These formats are the same, apart from Ntr being little-endian and Rvl being big-endian.
22//! [^ff]: The official format is Ca**f**e **F**ace **S**tore **D**ata, probably due to CFSD being taken by Ctr <sup>[src](https://github.com/HEYimHeroic/MiiDataFiles)</sup>.
23//! [^mnms]: Stored in the browser's `localStorage`. Often shared as a base64 string, or sometimes saved with the `.mnms` extension.
24//!
25//! # Conversion
26//!
27//! <div class="warning">
28//!
29//! **Under Construction**
30//!
31//! This part of the library is still in works.
32//! You can instantiate a GenericChar, but
33//! conversions have not been implemented yet.
34//!
35//! </div>
36//!
37//! This library provides a [`GenericChar`] struct, which provides a common ground for Char data formats.
38//! Due to the changes in shape, color and texture indices between {Rvl, Ntr} and later formats,
39//! only a one-way conversion can be infallably performed.
40//!
41#![doc = svgbobdoc::transform!(
42/// <center>
43/// ```svgbob
44///                                               ┌────────────────┐
45///                                               │                │
46///                                 ┌────────────►│  CtrStoreData  │
47/// ┌───────────┐                   │             │                │
48/// │           │                   │             └────────────────┘
49/// │  NSD,NCD  │                   ▼
50/// │           ├────────►┌───────────────┐        ┌──────────────┐
51/// └────────┬──┘         │               │        │              │
52///    ▲     ▼            │  GenericChar  │◄──────►│  NxCharInfo  │
53/// ┌──┴────────┐         │               │        │              │
54/// │           ├────────►└───────────────┘        └──────────────┘
55/// │  RSD,RCD  │                   ▲
56/// │           │                   │             ┌────────────────┐
57/// └───────────┘                   │             │                │
58///                                 └────────────►│  WsLocalStore  │
59///                                               │                │
60///                                               └────────────────┘
61/// ```
62/// </center>
63)]
64//!
65//! # Usage
66//!
67//! ```no_run
68//! use std::{env, fs::File, error::Error};
69//! use vee_parse::{NxCharInfo, BinRead};
70//!
71//! fn main() -> Result<(), Box<dyn Error>> {
72//!     let charinfo_path = "./Alice.charinfo";
73//!     let mut file = File::open(&charinfo_path)?;
74//!
75//!     let nx_char = NxCharInfo::read(&mut file)?;
76//!
77//!     let name = nx_char.nickname.to_string(); // "Alice"
78//!
79//!     Ok(())
80//! }
81//!
82//!
83//! ```
84
85pub mod ctr;
86pub mod generic;
87pub mod nx;
88pub mod rvl_ntr;
89
90pub use binrw::{BinRead, NullWideString, binrw};
91pub use ctr::CtrStoreData;
92pub use generic::GenericChar;
93pub use nx::NxCharInfo;
94pub use rvl_ntr::NtrCharData;
95pub use rvl_ntr::NtrStoreData;
96pub use rvl_ntr::RvlCharData;
97pub use rvl_ntr::RvlStoreData;
98
99/// A UTF-16 String with a fixed length and non-enforced null termination.
100/// The string is allowed to reach the maximum length without a null terminator,
101/// and any nulls are stripped.
102#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
103#[binrw]
104#[repr(transparent)]
105pub struct FixedLengthWideString<const CHARS: usize>(pub [u16; CHARS]);
106
107impl<const N: usize> std::fmt::Debug for FixedLengthWideString<N> {
108    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109        write!(f, "FixedLengthWideString(\"{}\")", self)
110    }
111}
112
113impl<const N: usize> std::fmt::Display for FixedLengthWideString<N> {
114    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115        write!(f, "{}", self.parse_utf16())
116    }
117}
118
119impl<const N: usize> FixedLengthWideString<N> {
120    /// Will fail on big endian hardware. Wait for `str_from_utf16_endian` to drop.
121    fn parse_utf16(self) -> String {
122        String::from_utf16(&self.0[..])
123            .expect(
124                "UTF-16 string parse error. Parsing little endian string on big endian hardware?",
125            )
126            .replace("\0", "")
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use crate::{CtrStoreData, NxCharInfo, RvlCharData, rvl_ntr::FavoriteColor};
133    use binrw::BinRead;
134    use std::{error::Error, fs::File};
135
136    type R = Result<(), Box<dyn Error>>;
137
138    #[test]
139    fn nx_deser() -> R {
140        let mut mii = File::open(format!(
141            "{}/resources_here/j0.charinfo",
142            std::env::var("CARGO_WORKSPACE_DIR").unwrap()
143        ))?;
144
145        let mii = NxCharInfo::read(&mut mii)?;
146
147        assert_eq!(mii.nickname.to_string(), "Jo Null".to_string());
148
149        assert_eq!(mii.glass_color.0, 17);
150        assert_eq!(mii.reserved, 0);
151
152        Ok(())
153    }
154
155    #[test]
156    fn ctr_deser() -> R {
157        let mut mii = File::open(format!(
158            "{}/resources_here/j0.ffsd",
159            std::env::var("CARGO_WORKSPACE_DIR").unwrap()
160        ))?;
161
162        let mii = CtrStoreData::read(&mut mii)?;
163
164        assert_eq!(mii.name.to_string(), "Jo Null".to_string());
165        assert_eq!(mii.personal_info_2.favorite_color().value(), 8);
166
167        Ok(())
168    }
169
170    #[test]
171    fn rvl_ntr_deser() -> R {
172        let mut rvl = File::open(format!(
173            "{}/resources_here/Jain.rcd",
174            std::env::var("CARGO_WORKSPACE_DIR").unwrap_or(
175                std::env::current_dir()
176                    .unwrap()
177                    .to_string_lossy()
178                    .to_string()
179            )
180        ))?;
181        let rvl = RvlCharData::read(&mut rvl)?;
182
183        assert_eq!(rvl.name.to_string(), "Jain".to_string());
184
185        let mut ntr = File::open(format!(
186            "{}/resources_here/Jain.rcd",
187            std::env::var("CARGO_WORKSPACE_DIR").unwrap_or(
188                std::env::current_dir()
189                    .unwrap()
190                    .to_string_lossy()
191                    .to_string()
192            )
193        ))?;
194        let ntr = RvlCharData::read(&mut ntr)?;
195
196        assert_eq!(ntr.name.to_string(), "Jain".to_string());
197        assert_eq!(ntr.name.to_string(), rvl.name.to_string());
198
199        assert_eq!(ntr.personal_info.favorite_color(), FavoriteColor::Purple);
200        assert_eq!(
201            ntr.personal_info.favorite_color(),
202            rvl.personal_info.favorite_color()
203        );
204
205        Ok(())
206    }
207}