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}