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

1//! Definitions for the `OpenFile` packet type, and it's response types.
2//!
3//! This doesn't _Read_ any data out of the file, but merely opens it for
4//! reading, or writing.
5
6use crate::{
7	errors::NetworkParseError,
8	fsemul::pcfs::errors::{PcfsApiError, SataProtocolError},
9};
10use bytes::{Bytes, BytesMut};
11use std::ffi::CStr;
12use valuable::{Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit};
13
14/// A packet to open a file.
15#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct SataOpenFilePacketBody {
17	/// The current mode string.
18	mode_string: String,
19	/// The path to query, note that this is not the 'resolved' path which is
20	/// the path to actual read from.
21	///
22	/// Interpolation has a few known ways of being replaced:
23	///
24	/// - `%MLC_EMU_DIR`: `<cafe_sdk>/data/mlc/`
25	/// - `%SLC_EMU_DIR`: `<cafe_sdk>/data/slc/`
26	/// - `%DISC_EMU_DIR`: `<cafe_sdk>/data/disc/`
27	/// - `%SAVE_EMU_DIR`: `<cafe_sdk>/data/save/`
28	/// - `%NETWORK`: <mounted network share path>
29	path: String,
30}
31
32impl SataOpenFilePacketBody {
33	/// Attempt to construct a new open file packet.
34	///
35	/// ## Errors
36	///
37	/// If the path is longer than 511 bytes. Normally the max path is 512 bytes,
38	/// but because we need to encode our data as a C-String with a NUL
39	/// terminator we cannot be longer than 511 bytes.
40	///
41	/// Consider using relative/mapped paths if possible when dealing with long
42	/// paths.
43	///
44	/// We will also error if the mode string is not valid, it requires to be in
45	/// the format of: `(r|w|a)(b+)+` (e.g. starts with either 'rwa', and then
46	/// has either `b`, or `+` after that one or more times).
47	pub fn new(path: String, mode_string: String) -> Result<Self, PcfsApiError> {
48		if path.len() > 511 {
49			return Err(PcfsApiError::PathTooLong(path));
50		}
51		for (idx, car) in mode_string.chars().enumerate() {
52			if idx == 0 && !['r', 'w', 'a'].contains(&car) {
53				return Err(PcfsApiError::BadModeString(mode_string));
54			}
55			if idx > 2 {
56				return Err(PcfsApiError::BadModeString(mode_string));
57			}
58			if idx != 0 && !['b', '+'].contains(&car) {
59				return Err(PcfsApiError::BadModeString(mode_string));
60			}
61		}
62
63		Ok(Self { mode_string, path })
64	}
65
66	#[must_use]
67	pub fn mode(&self) -> &str {
68		self.mode_string.as_str()
69	}
70
71	/// Update the path to send in this particular create directory packet.
72	///
73	/// ## Errors
74	///
75	/// We will also error if the mode string is not valid, it requires to be in
76	/// the format of: `(r|w|a)(b+)+` (e.g. starts with either 'rwa', and then
77	/// has either `b`, or `+` after that one or more times).
78	pub fn set_mode(&mut self, new_mode: String) -> Result<(), PcfsApiError> {
79		for (idx, car) in new_mode.chars().enumerate() {
80			if idx == 0 && !['r', 'w', 'a'].contains(&car) {
81				return Err(PcfsApiError::BadModeString(new_mode));
82			}
83			if idx > 2 {
84				return Err(PcfsApiError::BadModeString(new_mode));
85			}
86			if idx != 0 && !['b', '+'].contains(&car) {
87				return Err(PcfsApiError::BadModeString(new_mode));
88			}
89		}
90
91		self.mode_string = new_mode;
92		Ok(())
93	}
94
95	#[must_use]
96	pub fn path(&self) -> &str {
97		self.path.as_str()
98	}
99
100	/// Update the path to send in this particular open file packet.
101	///
102	/// ## Errors
103	///
104	/// If the path is longer than 511 bytes. Normally the max path is 512 bytes,
105	/// but because we need to encode our data as a C-String with a NUL
106	/// terminator we cannot be longer than 511 bytes.
107	///
108	/// Consider using relative/mapped paths if possible when dealing with long
109	/// paths.
110	pub fn set_path(&mut self, new_path: String) -> Result<(), PcfsApiError> {
111		if new_path.len() > 511 {
112			return Err(PcfsApiError::PathTooLong(new_path));
113		}
114
115		self.path = new_path;
116		Ok(())
117	}
118}
119
120impl From<&SataOpenFilePacketBody> for Bytes {
121	fn from(value: &SataOpenFilePacketBody) -> Self {
122		let mut result = BytesMut::with_capacity(0x210);
123		result.extend_from_slice(value.mode_string.as_bytes());
124		// Our mode strings are always shorter than the fully padded,
125		// even when you include we need to add a NUL terminator.
126		//
127		// Since NUL terminator and our padding at the same, just pad
128		// all at once.
129		result.extend(BytesMut::with_capacity(0x10 - result.len()));
130		result.extend_from_slice(value.path.as_bytes());
131		// These are C Strings so we need a NUL terminator.
132		// Pad with `0`, til we get a full path with a nul terminator.
133		result.extend(BytesMut::zeroed(0x210 - result.len()));
134		result.freeze()
135	}
136}
137
138impl From<SataOpenFilePacketBody> for Bytes {
139	fn from(value: SataOpenFilePacketBody) -> Self {
140		Self::from(&value)
141	}
142}
143
144impl TryFrom<Bytes> for SataOpenFilePacketBody {
145	type Error = NetworkParseError;
146
147	fn try_from(value: Bytes) -> Result<Self, Self::Error> {
148		if value.len() < 0x210 {
149			return Err(NetworkParseError::FieldNotLongEnough(
150				"SataOpenFile",
151				"Body",
152				0x210,
153				value.len(),
154				value,
155			));
156		}
157		if value.len() > 0x210 {
158			return Err(NetworkParseError::UnexpectedTrailer(
159				"SataOpenFile",
160				value.slice(0x210..),
161			));
162		}
163
164		let (mode_bytes, path_bytes) = value.split_at(0x10);
165		let mode_c_str =
166			CStr::from_bytes_until_nul(mode_bytes).map_err(NetworkParseError::BadCString)?;
167		let path_c_str =
168			CStr::from_bytes_until_nul(path_bytes).map_err(NetworkParseError::BadCString)?;
169		let final_mode = mode_c_str.to_str()?.to_owned();
170		for (idx, car) in final_mode.chars().enumerate() {
171			if idx == 0 && !['r', 'w', 'a'].contains(&car) {
172				return Err(SataProtocolError::BadModeString(final_mode).into());
173			}
174			if idx > 2 {
175				return Err(SataProtocolError::BadModeString(final_mode).into());
176			}
177			if idx != 0 && !['b', '+'].contains(&car) {
178				return Err(SataProtocolError::BadModeString(final_mode).into());
179			}
180		}
181
182		Ok(Self {
183			mode_string: final_mode,
184			path: path_c_str.to_str()?.to_owned(),
185		})
186	}
187}
188
189const SATA_OPEN_FILE_PACKET_BODY_FIELDS: &[NamedField<'static>] = &[NamedField::new("path")];
190
191impl Structable for SataOpenFilePacketBody {
192	fn definition(&self) -> StructDef<'_> {
193		StructDef::new_static(
194			"SataOpenFilePacketBody",
195			Fields::Named(SATA_OPEN_FILE_PACKET_BODY_FIELDS),
196		)
197	}
198}
199
200impl Valuable for SataOpenFilePacketBody {
201	fn as_value(&self) -> Value<'_> {
202		Value::Structable(self)
203	}
204
205	fn visit(&self, visitor: &mut dyn Visit) {
206		visitor.visit_named_fields(&NamedValues::new(
207			SATA_OPEN_FILE_PACKET_BODY_FIELDS,
208			&[Valuable::as_value(&self.path)],
209		));
210	}
211}