use std::{
io::{BufWriter, Write},
net::{Ipv4Addr, SocketAddr, TcpStream},
time::Duration,
};
use thiserror::Error;
const WIILOAD_PORT: u16 = 4299;
const WIILOAD_MAGIC: &[u8] = b"HAXX";
const WIILOAD_VERSION: [u8; 3] = [0, 5, 0];
const WIILOAD_TIMEOUT: Duration = Duration::from_secs(10);
#[derive(Error, Debug)]
pub enum WiiloadError {
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("Network error: {0}")]
Net(#[from] std::net::AddrParseError),
#[error("Timeout")]
Timeout,
#[error("File too big")]
TryFromIntError(#[from] std::num::TryFromIntError),
#[error("Filename too long")]
FileNameTooLong,
}
fn push(
filename: &str,
body: &[u8],
wii_ip: Ipv4Addr,
uncompressed_size: u32,
) -> Result<(), WiiloadError> {
let compressed_size: u32 = body.len().try_into()?;
let filename_len: u8 = filename
.len()
.try_into()
.map_err(|_| WiiloadError::FileNameTooLong)?;
let wii_addr = SocketAddr::from((wii_ip, WIILOAD_PORT));
let mut stream = {
let stream = TcpStream::connect_timeout(&wii_addr, WIILOAD_TIMEOUT)?;
stream.set_read_timeout(Some(WIILOAD_TIMEOUT))?;
stream.set_write_timeout(Some(WIILOAD_TIMEOUT))?;
BufWriter::new(stream)
};
stream.write_all(WIILOAD_MAGIC)?;
stream.write_all(&WIILOAD_VERSION[..])?;
stream.write_all(&[filename_len])?;
stream.write_all(&compressed_size.to_be_bytes())?;
stream.write_all(&uncompressed_size.to_be_bytes())?;
stream.write_all(body)?;
stream.write_all(filename.as_bytes())?;
if !filename.ends_with('\0') {
stream.write_all(&[0])?;
}
stream.flush()?;
Ok(())
}
pub fn send(
filename: impl AsRef<str>,
body: impl AsRef<[u8]>,
wii_ip: impl Into<Ipv4Addr>,
) -> Result<(), WiiloadError> {
push(filename.as_ref(), body.as_ref(), wii_ip.into(), 0)
}
#[cfg(feature = "compression")]
pub fn compress_then_send(
filename: impl AsRef<str>,
body: impl AsRef<[u8]>,
wii_ip: impl Into<Ipv4Addr>,
) -> Result<(), WiiloadError> {
use miniz_oxide::deflate::compress_to_vec_zlib;
let body = body.as_ref();
let uncompressed_size = body.len().try_into()?;
let compressed_body = compress_to_vec_zlib(body, 9);
push(
filename.as_ref(),
&compressed_body,
wii_ip.into(),
uncompressed_size,
)
}