cat-dev 0.0.13

A library for interacting with the CAT-DEV hardware units distributed by Nintendo (i.e. a type of Wii-U DevKits).
Documentation
//! Handle an SDIO Telnet message coming in our stream.

use crate::{
	errors::CatBridgeError,
	fsemul::sdio::{
		errors::SdioNetworkError,
		proto::message::{
			SdioControlMessageRequest, SdioControlTelnetChannel, SdioControlTelnetData,
			SdioControlTelnetMessage,
		},
		server::{SDIO_PRINTF_BUFFS, SdioStreamState},
	},
	mion::proto::control::MionBootType,
	net::{
		additions::StreamID,
		server::requestable::{Body, State},
	},
};
use bytes::Bytes;
use std::sync::Arc;
use tokio::sync::{Mutex, oneshot::Sender as OneshotSender};
use tracing::{debug, info};

#[allow(
	// TODO(mythra): file issue with clippy, suggestion doesn't work.
	clippy::assigning_clones,
)]
pub(super) async fn handle_telnet_message(
	State(state): State<SdioStreamState>,
	stream_id: StreamID,
	Body(request): Body<SdioControlMessageRequest>,
) -> Result<Option<Bytes>, CatBridgeError> {
	for message in request.messages_owned() {
		match message.channel() {
			SdioControlTelnetChannel::CafeOS => {
				// Cos actually echo's to the SCT terminal.
				debug!(
					lisa.subsystem = "cos",
					"{}",
					message.buffer_as_complete_string()
				);
			}
			SdioControlTelnetChannel::DevkitMsg => {
				return handle_devkit_msg(
					state.active_hook.clone(),
					state.boot_type,
					&message.buffer_as_complete_string(),
				)
				.await;
			}
			SdioControlTelnetChannel::SysConfigTool => {
				let Some(mut buff) = SDIO_PRINTF_BUFFS
					.get_async(&(stream_id.to_raw(), message.channel()))
					.await
				else {
					return Err(SdioNetworkError::PrintfMissingBuffer(stream_id.to_raw()).into());
				};

				for item in message.buffer() {
					match item {
						SdioControlTelnetData::Backspace => {
							_ = buff.pop();
						}
						SdioControlTelnetData::ClearLine => {
							*buff = String::new();
						}
						SdioControlTelnetData::StringData(bytes) => {
							buff.push_str(&String::from_utf8_lossy(bytes));
						}
					}
				}

				while let Some(idx) = buff.find('\r') {
					let (my_line, new_buff) = buff.split_at(idx);
					let trimmed_line = my_line.trim();
					if !trimmed_line.is_empty()
						&& !trimmed_line.starts_with('#')
						&& !trimmed_line.starts_with("Note")
						&& !trimmed_line
							.contains("Please press F12 or the Backtick key to enter command.")
					{
						info!(lisa.subsystem = "sct", "{}", trimmed_line);
					}
					*buff = new_buff[1..].to_owned();
				}
			}
		}
	}

	Ok(None)
}

async fn handle_devkit_msg(
	active_marker: Option<Arc<Mutex<Option<OneshotSender<()>>>>>,
	boot_type: MionBootType,
	msg: &str,
) -> Result<Option<Bytes>, CatBridgeError> {
	if msg == "CAFE_BOOT_MODE" {
		if let Some(marker_ref) = active_marker {
			let mut lock = marker_ref.lock().await;
			if let Some(channel) = lock.take() {
				channel
					.send(())
					.map_err(|()| CatBridgeError::ClosedChannel)?;
			}
		}

		return Ok(Some(Bytes::try_from(SdioControlMessageRequest::new(
			vec![SdioControlTelnetMessage::new(
				match boot_type {
					MionBootType::DUAL | MionBootType::PCFS | MionBootType::NAND => {
						format!("{boot_type}")
					}
					// MION's don't understand 'UNK' default to something that we do know and is probably okay.
					MionBootType::Unk(_) => format!("{}", MionBootType::PCFS),
				},
				SdioControlTelnetChannel::DevkitMsg,
			)?],
		))?));
	}

	info!(lisa.subsystem = "dkm", "{}", msg.trim_end());
	Ok(None)
}