cat_dev/fsemul/pcfs/sata/proto/
change_mode.rs

1//! Definitions for the `ChangeMode` packet type, and it's response types.
2//!
3//! Although this implies you can set any arbitrary mode, unfortunately because
4//! the SDK only supported windows, and windows doesn't have full mode strings,
5//! we can only set read only. We're basically a toggle between 0444, and 0666.
6
7use crate::{errors::NetworkParseError, fsemul::pcfs::errors::PcfsApiError};
8use bytes::{BufMut, Bytes, BytesMut};
9use std::ffi::CStr;
10use valuable::{Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit};
11
12/// A packet to get change read-only state of a path.
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct SataChangeModePacketBody {
15	/// The path to query, note that this is not the 'resolved' path which is
16	/// the path to actual read from.
17	///
18	/// Interpolation has a few known ways of being replaced:
19	///
20	/// - `%MLC_EMU_DIR`: `<cafe_sdk>/data/mlc/`
21	/// - `%SLC_EMU_DIR`: `<cafe_sdk>/data/slc/`
22	/// - `%DISC_EMU_DIR`: `<cafe_sdk>/data/disc/`
23	/// - `%SAVE_EMU_DIR`: `<cafe_sdk>/data/save/`
24	/// - `%NETWORK`: <mounted network share path>
25	path: String,
26	/// If we're setting the write mode.
27	set_write_mode: bool,
28}
29
30impl SataChangeModePacketBody {
31	/// Attempt to construct a new change mode packet.
32	///
33	/// ## Errors
34	///
35	/// If the path is longer than 511 bytes. Normally the max path is 512 bytes,
36	/// but because we need to encode our data as a C-String with a NUL
37	/// terminator we cannot be longer than 511 bytes.
38	///
39	/// Consider using relative/mapped paths if possible when dealing with long
40	/// paths.
41	pub fn new(path: String, set_write_mode: bool) -> Result<Self, PcfsApiError> {
42		if path.len() > 511 {
43			return Err(PcfsApiError::PathTooLong(path));
44		}
45
46		Ok(Self {
47			path,
48			set_write_mode,
49		})
50	}
51
52	#[must_use]
53	pub fn path(&self) -> &str {
54		self.path.as_str()
55	}
56
57	/// Update the path to send in this particular change mode packet.
58	///
59	/// ## Errors
60	///
61	/// If the path is longer than 511 bytes. Normally the max path is 512 bytes,
62	/// but because we need to encode our data as a C-String with a NUL
63	/// terminator we cannot be longer than 511 bytes.
64	///
65	/// Consider using relative/mapped paths if possible when dealing with long
66	/// paths.
67	pub fn set_path(&mut self, new_path: String) -> Result<(), PcfsApiError> {
68		if new_path.len() > 511 {
69			return Err(PcfsApiError::PathTooLong(new_path));
70		}
71
72		self.path = new_path;
73		Ok(())
74	}
75
76	#[must_use]
77	pub const fn will_set_write_mode(&self) -> bool {
78		self.set_write_mode
79	}
80
81	/// Update the `set_write_mode` flag, which determines if we'll set the file
82	/// as writable or not.
83	pub const fn set_write_mode(&mut self, will_set: bool) {
84		self.set_write_mode = will_set;
85	}
86}
87
88impl TryFrom<Bytes> for SataChangeModePacketBody {
89	type Error = NetworkParseError;
90
91	fn try_from(value: Bytes) -> Result<Self, Self::Error> {
92		if value.len() < 0x204 {
93			return Err(NetworkParseError::FieldNotLongEnough(
94				"SataChangeMode",
95				"Body",
96				0x204,
97				value.len(),
98				value,
99			));
100		}
101		if value.len() > 0x204 {
102			return Err(NetworkParseError::UnexpectedTrailer(
103				"SataChangeMode",
104				value.slice(0x204..),
105			));
106		}
107
108		let (path_bytes, num) = value.split_at(0x200);
109		let path_c_str =
110			CStr::from_bytes_until_nul(path_bytes).map_err(NetworkParseError::BadCString)?;
111		let write_mode_flags = u32::from_be_bytes([num[0], num[1], num[2], num[3]]);
112		let final_path = path_c_str.to_str()?.to_owned();
113
114		Ok(Self {
115			path: final_path,
116			set_write_mode: write_mode_flags & 0x222 != 0,
117		})
118	}
119}
120
121impl From<&SataChangeModePacketBody> for Bytes {
122	fn from(value: &SataChangeModePacketBody) -> Self {
123		let mut result = BytesMut::with_capacity(0x204);
124		result.extend_from_slice(value.path.as_bytes());
125		// These are C Strings so we need a NUL terminator.
126		// Pad with `0`, til we get a full path with a nul terminator.
127		result.extend(BytesMut::zeroed(0x200 - result.len()));
128		result.put_u32(if value.set_write_mode { 0x666 } else { 0x444 });
129		result.freeze()
130	}
131}
132
133impl From<SataChangeModePacketBody> for Bytes {
134	fn from(value: SataChangeModePacketBody) -> Self {
135		Self::from(&value)
136	}
137}
138
139const SATA_CHANGE_MODE_PACKET_BODY_FIELDS: &[NamedField<'static>] = &[NamedField::new("path")];
140
141impl Structable for SataChangeModePacketBody {
142	fn definition(&self) -> StructDef<'_> {
143		StructDef::new_static(
144			"SataChangeModePacketBody",
145			Fields::Named(SATA_CHANGE_MODE_PACKET_BODY_FIELDS),
146		)
147	}
148}
149
150impl Valuable for SataChangeModePacketBody {
151	fn as_value(&self) -> Value<'_> {
152		Value::Structable(self)
153	}
154
155	fn visit(&self, visitor: &mut dyn Visit) {
156		visitor.visit_named_fields(&NamedValues::new(
157			SATA_CHANGE_MODE_PACKET_BODY_FIELDS,
158			&[Valuable::as_value(&self.path)],
159		));
160	}
161}