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

1//! Definitions for the `OpenFolder` packet type, and it's response types.
2//!
3//! This does not iterate over the directory at all, just opens the directory.
4
5use crate::{errors::NetworkParseError, fsemul::pcfs::errors::PcfsApiError};
6use bytes::{Bytes, BytesMut};
7use std::ffi::CStr;
8use valuable::{Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit};
9
10/// A packet to open an iterator over a folders contents.
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct SataOpenFolderPacketBody {
13	/// The path to query, note that this is not the 'resolved' path which is
14	/// the path to actual read from.
15	///
16	/// Interpolation has a few known ways of being replaced:
17	///
18	/// - `%MLC_EMU_DIR`: `<cafe_sdk>/data/mlc/`
19	/// - `%SLC_EMU_DIR`: `<cafe_sdk>/data/slc/`
20	/// - `%DISC_EMU_DIR`: `<cafe_sdk>/data/disc/`
21	/// - `%SAVE_EMU_DIR`: `<cafe_sdk>/data/save/`
22	/// - `%NETWORK`: <mounted network share path>
23	path: String,
24}
25
26impl SataOpenFolderPacketBody {
27	/// Attempt to construct a new open folder packet.
28	///
29	/// ## Errors
30	///
31	/// If the path is longer than 511 bytes. Normally the max path is 512 bytes,
32	/// but because we need to encode our data as a C-String with a NUL
33	/// terminator we cannot be longer than 511 bytes.
34	///
35	/// Consider using relative/mapped paths if possible when dealing with long
36	/// paths.
37	pub fn new(path: String) -> Result<Self, PcfsApiError> {
38		if path.len() > 511 {
39			return Err(PcfsApiError::PathTooLong(path));
40		}
41
42		Ok(Self { path })
43	}
44
45	#[must_use]
46	pub fn path(&self) -> &str {
47		self.path.as_str()
48	}
49
50	/// Update the path to send in this particular open folder packet.
51	///
52	/// ## Errors
53	///
54	/// If the path is longer than 511 bytes. Normally the max path is 512 bytes,
55	/// but because we need to encode our data as a C-String with a NUL
56	/// terminator we cannot be longer than 511 bytes.
57	///
58	/// Consider using relative/mapped paths if possible when dealing with long
59	/// paths.
60	pub fn set_path(&mut self, new_path: String) -> Result<(), PcfsApiError> {
61		if new_path.len() > 511 {
62			return Err(PcfsApiError::PathTooLong(new_path));
63		}
64
65		self.path = new_path;
66		Ok(())
67	}
68}
69
70impl From<&SataOpenFolderPacketBody> for Bytes {
71	fn from(value: &SataOpenFolderPacketBody) -> Self {
72		let mut result = BytesMut::with_capacity(0x200);
73		result.extend_from_slice(value.path.as_bytes());
74		// These are C Strings so we need a NUL terminator.
75		// Pad with `0`, til we get a full path with a nul terminator.
76		result.extend(BytesMut::zeroed(0x200 - result.len()));
77		result.freeze()
78	}
79}
80
81impl From<SataOpenFolderPacketBody> for Bytes {
82	fn from(value: SataOpenFolderPacketBody) -> Self {
83		Self::from(&value)
84	}
85}
86
87impl TryFrom<Bytes> for SataOpenFolderPacketBody {
88	type Error = NetworkParseError;
89
90	fn try_from(value: Bytes) -> Result<Self, Self::Error> {
91		if value.len() < 0x200 {
92			return Err(NetworkParseError::FieldNotLongEnough(
93				"SataOpenFolder",
94				"Body",
95				0x200,
96				value.len(),
97				value,
98			));
99		}
100		if value.len() > 0x200 {
101			return Err(NetworkParseError::UnexpectedTrailer(
102				"SataOpenFolder",
103				value.slice(0x200..),
104			));
105		}
106
107		let path_c_str =
108			CStr::from_bytes_until_nul(&value).map_err(NetworkParseError::BadCString)?;
109
110		Ok(Self {
111			path: path_c_str.to_str()?.to_owned(),
112		})
113	}
114}
115
116const SATA_OPEN_FOLDER_PACKET_BODY_FIELDS: &[NamedField<'static>] = &[NamedField::new("path")];
117
118impl Structable for SataOpenFolderPacketBody {
119	fn definition(&self) -> StructDef<'_> {
120		StructDef::new_static(
121			"SataOpenFolderPacketBody",
122			Fields::Named(SATA_OPEN_FOLDER_PACKET_BODY_FIELDS),
123		)
124	}
125}
126
127impl Valuable for SataOpenFolderPacketBody {
128	fn as_value(&self) -> Value<'_> {
129		Value::Structable(self)
130	}
131
132	fn visit(&self, visitor: &mut dyn Visit) {
133		visitor.visit_named_fields(&NamedValues::new(
134			SATA_OPEN_FOLDER_PACKET_BODY_FIELDS,
135			&[Valuable::as_value(&self.path)],
136		));
137	}
138}