1use crate::mion::proto::{
5 control::MionBootType, images::MionDiscImageType, parameter::MionParameterAPIError,
6};
7use bytes::Bytes;
8use valuable::{Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit};
9
10#[derive(Clone, Debug, Hash, PartialEq, Eq)]
12pub enum ParameterLocationSpecification {
13 NameLike(String),
15 Index(u16),
17}
18
19impl TryFrom<&str> for ParameterLocationSpecification {
20 type Error = MionParameterAPIError;
21
22 fn try_from(value: &str) -> Result<Self, Self::Error> {
23 if index_from_parameter_name(value).is_some() {
24 Ok(Self::NameLike(value.to_owned()))
25 } else {
26 Err(MionParameterAPIError::NameNotKnown(value.to_owned()))
27 }
28 }
29}
30impl TryFrom<&String> for ParameterLocationSpecification {
31 type Error = MionParameterAPIError;
32
33 fn try_from(value: &String) -> Result<Self, Self::Error> {
34 Self::try_from(value.as_str())
35 }
36}
37impl TryFrom<String> for ParameterLocationSpecification {
38 type Error = MionParameterAPIError;
39
40 fn try_from(value: String) -> Result<Self, Self::Error> {
41 Self::try_from(value.as_str())
42 }
43}
44
45impl TryFrom<u16> for ParameterLocationSpecification {
46 type Error = MionParameterAPIError;
47
48 fn try_from(value: u16) -> Result<Self, Self::Error> {
49 if value < 512 {
50 Ok(Self::Index(value))
51 } else {
52 Err(MionParameterAPIError::NotInRange(usize::from(value)))
53 }
54 }
55}
56
57#[must_use]
59pub fn index_from_parameter_name(name: &str) -> Option<usize> {
60 if let Ok(number) = name.parse::<usize>()
61 && number < 512
62 {
63 return Some(number);
64 }
65
66 match name {
67 "nand-mode" | "nand_mode" | "nandmode" | "nand mode" => Some(2),
68 "sdk-major" | "sdk_major" | "sdk major" | "sdk-major-version" | "sdk_major_version"
69 | "sdk major version" | "major-version" | "major_version" | "major version" | "major" => Some(3),
70 "sdk-minor" | "sdk_minor" | "sdk minor" | "sdk-minor-version" | "sdk_minor_version"
71 | "sdk minor version" | "minor-version" | "minor_version" | "minor version" | "minor" => Some(4),
72 "sdk-misc" | "sdk_misc" | "sdk misc" | "sdk-misc-version" | "sdk_misc_version"
73 | "sdk misc version" | "misc-version" | "misc_version" | "misc version" | "misc" => Some(5),
74 "bank-0" | "bank_0" | "bank 0" => Some(100),
75 "bank-1" | "bank_1" | "bank 1" => Some(101),
76 "bank-2" | "bank_2" | "bank 2" => Some(102),
77 "bank-3" | "bank_3" | "bank 3" => Some(103),
78 "bank-4" | "bank_4" | "bank 4" => Some(104),
79 "bank-5" | "bank_5" | "bank 5" => Some(105),
80 "bank-6" | "bank_6" | "bank 6" => Some(106),
81 "bank-7" | "bank_7" | "bank 7" => Some(107),
82 "bank-8" | "bank_8" | "bank 8" => Some(108),
83 "bank-9" | "bank_9" | "bank 9" => Some(109),
84 "bank-10" | "bank_10" | "bank 10" => Some(110),
85 _ => None,
86 }
87}
88
89#[must_use]
91pub fn validate_value_at_index(
92 specification: &ParameterLocationSpecification,
93 byte_value: u8,
94) -> bool {
95 let index = match specification {
96 ParameterLocationSpecification::Index(idx) => usize::from(*idx),
97 ParameterLocationSpecification::NameLike(name) => {
98 if let Some(idx) = index_from_parameter_name(name) {
99 idx
100 } else {
101 return false;
102 }
103 }
104 };
105
106 match index {
107 2 => match MionBootType::from(byte_value) {
108 MionBootType::NAND | MionBootType::PCFS | MionBootType::DUAL => true,
109 MionBootType::Unk(_) => false,
110 },
111 100..=110 => byte_value >= 254,
116 _ => true,
118 }
119}
120
121const PARAMETER_DUMP_FIELDS: &[NamedField<'static>] = &[
122 NamedField::new("NandMode"),
123 NamedField::new("SdkMajor"),
124 NamedField::new("SdkMinor"),
125 NamedField::new("SdkMisc"),
126 NamedField::new("Bank0"),
127 NamedField::new("Bank1"),
128 NamedField::new("Bank2"),
129 NamedField::new("Bank3"),
130 NamedField::new("Bank4"),
131 NamedField::new("Bank5"),
132 NamedField::new("Bank6"),
133 NamedField::new("Bank7"),
134 NamedField::new("Bank8"),
135 NamedField::new("Bank9"),
136 NamedField::new("Bank10"),
137 NamedField::new("UnknownParameters"),
138];
139const KNOWN_INDEXES: &[usize] = &[
140 2_usize, 3_usize, 4_usize, 5_usize, 100_usize, 101_usize, 102_usize, 103_usize, 104_usize,
141 105_usize, 106_usize, 107_usize, 108_usize, 109_usize, 110_usize,
142];
143pub struct ValuableParameterDump<'value>(pub &'value Bytes);
144impl Structable for ValuableParameterDump<'_> {
145 fn definition(&self) -> StructDef<'_> {
146 StructDef::new_static(
147 "ValuableParameterDump",
148 Fields::Named(PARAMETER_DUMP_FIELDS),
149 )
150 }
151}
152impl Valuable for ValuableParameterDump<'_> {
153 fn as_value(&self) -> valuable::Value<'_> {
154 Value::Structable(self)
155 }
156
157 fn visit(&self, visitor: &mut dyn Visit) {
158 let mut unknown_params = Vec::with_capacity(self.0.len() - KNOWN_INDEXES.len());
159 for (idx, byte) in self.0.iter().enumerate() {
160 if KNOWN_INDEXES.contains(&idx) {
161 continue;
162 }
163 unknown_params.push((idx, *byte));
164 }
165
166 visitor.visit_named_fields(&NamedValues::new(
167 PARAMETER_DUMP_FIELDS,
168 &[
169 Valuable::as_value(&MionBootType::from(self.0[KNOWN_INDEXES[0]])),
170 Valuable::as_value(&self.0[KNOWN_INDEXES[1]]),
171 Valuable::as_value(&self.0[KNOWN_INDEXES[2]]),
172 Valuable::as_value(&self.0[KNOWN_INDEXES[3]]),
173 Valuable::as_value(&MionDiscImageType::from(self.0[KNOWN_INDEXES[4]])),
174 Valuable::as_value(&MionDiscImageType::from(self.0[KNOWN_INDEXES[5]])),
175 Valuable::as_value(&MionDiscImageType::from(self.0[KNOWN_INDEXES[6]])),
176 Valuable::as_value(&MionDiscImageType::from(self.0[KNOWN_INDEXES[7]])),
177 Valuable::as_value(&MionDiscImageType::from(self.0[KNOWN_INDEXES[8]])),
178 Valuable::as_value(&MionDiscImageType::from(self.0[KNOWN_INDEXES[9]])),
179 Valuable::as_value(&MionDiscImageType::from(self.0[KNOWN_INDEXES[10]])),
180 Valuable::as_value(&MionDiscImageType::from(self.0[KNOWN_INDEXES[11]])),
181 Valuable::as_value(&MionDiscImageType::from(self.0[KNOWN_INDEXES[12]])),
182 Valuable::as_value(&MionDiscImageType::from(self.0[KNOWN_INDEXES[13]])),
183 Valuable::as_value(&MionDiscImageType::from(self.0[KNOWN_INDEXES[14]])),
184 Valuable::as_value(&unknown_params),
185 ],
186 ));
187 }
188}
189
190#[cfg(test)]
191mod unit_tests {
192 use super::*;
193 use bytes::BytesMut;
194 use valuable::Visit;
195
196 #[test]
197 pub fn can_map_parameter_name_to_index() {
198 for (name, expected_index) in vec![
199 ("nand-mode", Some(2)),
200 ("nand_mode", Some(2)),
201 ("nandmode", Some(2)),
202 ("nand mode", Some(2)),
203 ("sdk-major", Some(3)),
204 ("sdk_major", Some(3)),
205 ("sdk major", Some(3)),
206 ("sdk-major-version", Some(3)),
207 ("sdk_major_version", Some(3)),
208 ("sdk major version", Some(3)),
209 ("major-version", Some(3)),
210 ("major_version", Some(3)),
211 ("major version", Some(3)),
212 ("major", Some(3)),
213 ("sdk-minor", Some(4)),
214 ("sdk_minor", Some(4)),
215 ("sdk minor", Some(4)),
216 ("sdk-minor-version", Some(4)),
217 ("sdk_minor_version", Some(4)),
218 ("sdk minor version", Some(4)),
219 ("minor-version", Some(4)),
220 ("minor_version", Some(4)),
221 ("minor version", Some(4)),
222 ("minor", Some(4)),
223 ("sdk-misc", Some(5)),
224 ("sdk_misc", Some(5)),
225 ("sdk misc", Some(5)),
226 ("sdk-misc-version", Some(5)),
227 ("sdk_misc_version", Some(5)),
228 ("sdk misc version", Some(5)),
229 ("misc-version", Some(5)),
230 ("misc_version", Some(5)),
231 ("misc version", Some(5)),
232 ("misc", Some(5)),
233 ("trust me babes", None),
234 ("mics", None),
235 ] {
236 assert_eq!(
237 index_from_parameter_name(name),
238 expected_index,
239 "Parameter name: {name} expected to map to index: {expected_index:?}, but did not get that!",
240 );
241 }
242
243 for num in 0..512 {
244 let displayed = format!("{num}");
245 assert_eq!(
246 index_from_parameter_name(&displayed),
247 Some(num),
248 "Parameter name from index as string should be mapped to self!",
249 );
250 }
251
252 for num in 512..1024 {
253 let displayed = format!("{num}");
254 assert_eq!(
255 index_from_parameter_name(&displayed),
256 None,
257 "Invalid Parameter index number should not map to any value!",
258 );
259 }
260 }
261
262 #[test]
263 pub fn properly_parses_name_fields() {
264 struct AssertableVisitor;
265 impl Visit for AssertableVisitor {
266 fn visit_named_fields(&mut self, named_values: &NamedValues<'_>) {
267 for name in PARAMETER_DUMP_FIELDS {
268 assert!(
269 named_values.get_by_name(name.name()).is_some(),
270 "Parameter visitor did not pass a visit that had a required named field!"
271 );
272 }
273 }
274
275 fn visit_value(&mut self, value: Value<'_>) {
276 match value {
277 Value::Structable(v) => {
278 v.visit(self);
279 }
280 Value::Enumerable(v) => {
281 v.visit(self);
282 }
283 Value::Listable(v) => {
284 v.visit(self);
285 }
286 Value::Mappable(v) => {
287 v.visit(self);
288 }
289 _ => {}
290 }
291 }
292 }
293
294 let empty_param_set = BytesMut::zeroed(512).freeze();
295 let dumpee = ValuableParameterDump(&empty_param_set);
296 let mut visitor = AssertableVisitor;
297 dumpee.visit(&mut visitor);
298 }
299}