use std::error::Error;
use std::net::TcpStream;
use serde::{Deserialize, Serialize};
use strum_macros::Display;
use tungstenite::{connect, stream::MaybeTlsStream, Message, WebSocket};
#[derive(Display, Debug)]
pub enum Command {
AppVersion,
Name,
DeviceList,
Attach,
Info,
Boot,
Reset,
Menu,
List,
PutFile,
GetFile,
Rename,
Remove,
GetAddress,
}
#[derive(Display, Debug)]
#[allow(dead_code)]
pub enum Space {
SNES,
CMD,
}
#[derive(Debug, PartialEq)]
pub struct Infos {
pub version: String,
pub dev_type: String,
pub game: String,
pub flags: Vec<String>,
}
#[derive(Serialize)]
#[allow(non_snake_case)]
struct USB2SnesQuery {
Opcode: String,
Space: String,
Flags: Vec<String>,
Operands: Vec<String>,
}
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct USB2SnesResult {
Results: Vec<String>,
}
#[derive(Debug, PartialEq)]
pub enum USB2SnesFileType {
File = 0,
Dir = 1,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct USB2SnesEndpoint {
pub address: String,
pub port: u16,
}
impl Default for USB2SnesEndpoint {
fn default() -> Self {
USB2SnesEndpoint {
address: "127.0.0.1".to_string(),
port: 23074,
}
}
}
#[derive(Debug, PartialEq)]
pub struct USB2SnesFileInfo {
pub name: String,
pub file_type: USB2SnesFileType,
}
pub struct SyncClient {
client: WebSocket<MaybeTlsStream<TcpStream>>,
devel: bool,
}
impl SyncClient {
pub fn connect(endpoint: &USB2SnesEndpoint) -> Result<SyncClient, Box<dyn Error>> {
let ws = format!("ws://{}:{}", endpoint.address, endpoint.port);
let (client, _) = connect(ws).map_err(|e| Box::new(e) as Box<dyn Error>)?;
Ok(SyncClient {
client,
devel: false,
})
}
pub fn connect_with_devel(endpoint: &USB2SnesEndpoint) -> Result<SyncClient, Box<dyn Error>> {
let mut client = SyncClient::connect(endpoint)?;
client.devel = true;
Ok(client)
}
fn send_command(&mut self, command: Command, args: Vec<String>) -> Result<(), Box<dyn Error>> {
self.send_command_with_space(command, Space::SNES, args)?;
Ok(())
}
fn send_command_with_space(
&mut self,
command: Command,
space: Space,
args: Vec<String>,
) -> Result<(), Box<dyn Error>> {
if self.devel {
println!("Send command : {:?}", command);
}
let query = USB2SnesQuery {
Opcode: command.to_string(),
Space: space.to_string(),
Flags: vec![],
Operands: args,
};
let json = serde_json::to_string_pretty(&query)?;
if self.devel {
println!("{}", json);
}
let message = Message::text(json);
self.client.send(message)?;
Ok(())
}
fn get_reply(&mut self) -> Result<USB2SnesResult, Box<dyn Error>> {
let reply = self.client.read()?;
let textreply = match reply {
Message::Text(value) => value.to_string(),
_ => {
return Err("Error getting a reply test".into());
}
};
Ok(serde_json::from_str(&textreply)?)
}
pub fn set_name(&mut self, name: String) -> Result<(), Box<dyn Error>> {
self.send_command(Command::Name, vec![name])?;
Ok(())
}
pub fn app_version(&mut self) -> Result<String, Box<dyn Error>> {
self.send_command(Command::AppVersion, vec![])?;
let usbreply = self.get_reply()?;
Ok(usbreply.Results[0].to_string())
}
pub fn list_device(&mut self) -> Result<Vec<String>, Box<dyn Error>> {
self.send_command(Command::DeviceList, vec![])?;
let usbreply = self.get_reply()?;
Ok(usbreply.Results)
}
pub fn attach(&mut self, device: &String) -> Result<(), Box<dyn Error>> {
self.send_command(Command::Attach, vec![device.to_string()])?;
Ok(())
}
pub fn info(&mut self) -> Result<Infos, Box<dyn Error>> {
self.send_command(Command::Info, vec![])?;
let usbreply = self.get_reply()?;
let info: Vec<String> = usbreply.Results;
Ok(Infos {
version: info[0].clone(),
dev_type: info[1].clone(),
game: info[2].clone(),
flags: (info[3..].to_vec()),
})
}
pub fn reset(&mut self) -> Result<(), Box<dyn Error>> {
self.send_command(Command::Reset, vec![])?;
Ok(())
}
pub fn menu(&mut self) -> Result<(), Box<dyn Error>> {
self.send_command(Command::Menu, vec![])?;
Ok(())
}
pub fn boot(&mut self, toboot: &str) -> Result<(), Box<dyn Error>> {
self.send_command(Command::Boot, vec![toboot.to_owned()])?;
Ok(())
}
pub fn ls(&mut self, path: &String) -> Result<Vec<USB2SnesFileInfo>, Box<dyn Error>> {
self.send_command(Command::List, vec![path.to_string()])?;
let usbreply = self.get_reply()?;
let vec_info = usbreply.Results;
let mut toret: Vec<USB2SnesFileInfo> = vec![];
let mut i = 0;
while i < vec_info.len() {
let info: USB2SnesFileInfo = USB2SnesFileInfo {
file_type: if vec_info[i] == "1" {
USB2SnesFileType::File
} else {
USB2SnesFileType::Dir
},
name: vec_info[i + 1].to_string(),
};
toret.push(info);
i += 2;
}
Ok(toret)
}
pub fn send_file(&mut self, path: &String, data: Vec<u8>) -> Result<(), Box<dyn Error>> {
self.send_command(
Command::PutFile,
vec![path.to_string(), format!("{:x}", data.len())],
)?;
let mut start = 0;
let mut stop = 1024;
let data_len = data.len();
while start < data_len {
let odata = data[start..stop].to_owned();
let message = Message::binary(odata);
self.client.send(message)?;
start = stop;
stop += 1024;
if stop > data.len() {
stop = data.len();
}
}
Ok(())
}
pub fn get_file(&mut self, path: &str) -> Result<Vec<u8>, Box<dyn Error>> {
self.send_command(Command::GetFile, vec![path.to_owned()])?;
let usb2snes_reply = self.get_reply()?;
let string_hex = &usb2snes_reply.Results[0];
let size = usize::from_str_radix(string_hex, 16)?;
let mut data: Vec<u8> = Vec::with_capacity(size);
loop {
let reply = self.client.read()?;
match reply {
Message::Binary(msgdata) => {
data.extend(&msgdata);
}
_ => {
return Err(format!("Error getting file {}", path).into());
}
}
if data.len() == size {
break;
}
}
Ok(data)
}
pub fn remove_path(&mut self, path: &str) -> Result<(), Box<dyn Error>> {
self.send_command(Command::Remove, vec![path.to_owned()])?;
Ok(())
}
pub fn get_address(&mut self, address: u32, size: usize) -> Result<Vec<u8>, Box<dyn Error>> {
self.send_command_with_space(
Command::GetAddress,
Space::SNES,
vec![format!("{:x}", address), format!("{:x}", size)],
)?;
let mut data: Vec<u8> = Vec::with_capacity(size);
loop {
let reply = self.client.read()?;
match reply {
Message::Binary(msgdata) => {
data.extend(&msgdata);
}
_ => {
return Err(format!("Error getting a reply from address {:x}", &address).into())
}
}
if data.len() == size {
break;
}
}
Ok(data)
}
pub fn get_multi_address_as_vec(
&mut self,
addresses: Vec<u32>,
sizes: Vec<usize>,
) -> Result<Vec<u8>, Box<dyn Error>> {
let mut v_arg: Vec<String> = Vec::with_capacity(addresses.len() * 2);
let mut cpt = 0;
let mut total_size: usize = 0;
while cpt < addresses.len() {
v_arg.push(format!("{:x}", addresses[cpt]));
v_arg.push(format!("{:x}", sizes[cpt]));
total_size += sizes[cpt];
cpt += 1
}
self.send_command_with_space(Command::GetAddress, Space::SNES, v_arg)?;
let data = self.parse_multi_addresses(total_size)?;
Ok(data)
}
pub fn get_multi_address_from_pairs(
&mut self,
pairs: &[(u32, usize)],
) -> Result<Vec<Vec<u8>>, Box<dyn Error>> {
let mut args = vec![];
let mut total_size = 0;
for &(address, size) in pairs.iter() {
args.push(format!("{:x}", address));
args.push(format!("{:x}", size));
total_size += size;
}
self.send_command_with_space(Command::GetAddress, Space::SNES, args)?;
let data = self.parse_multi_addresses(total_size)?;
let mut ret: Vec<Vec<u8>> = vec![];
let mut consumed = 0;
for &(_address, size) in pairs.iter() {
ret.push(data[consumed..consumed + size].into());
consumed += size;
}
Ok(ret)
}
fn parse_multi_addresses(&mut self, size: usize) -> Result<Vec<u8>, Box<dyn Error>> {
let mut data: Vec<u8> = Vec::with_capacity(size);
loop {
let reply = self.client.read()?;
match reply {
Message::Binary(msgdata) => {
data.extend(&msgdata);
}
_ => return Err("Error parsing a reply from multiple addresses".into()),
}
if data.len() == size {
break;
}
}
Ok(data)
}
}