use tauri::State; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::{TcpListener, TcpStream}; use tokio_util::sync::CancellationToken; use crate::state::ProxyGuard; const PROXY_ADDR: &str = "proxy.mclegacyedition.xyz:2052"; //neo: yeah bro im hardcoding it async fn read_line(stream: &mut TcpStream) -> Result { let mut buf = Vec::new(); let mut byte = [0u8; 1]; loop { stream.read_exact(&mut byte).await.map_err(|e| e.to_string())?; if byte[0] == b'\n' { break; } if byte[0] != b'\r' { buf.push(byte[0]); } } String::from_utf8(buf).map_err(|e| e.to_string()) } async fn write_line(stream: &mut TcpStream, line: &str) -> Result<(), String> { let data = format!("{}\n", line); stream.write_all(data.as_bytes()).await.map_err(|e| e.to_string()) } async fn run_host_relay( _proxy_state: &ProxyGuard, proxy_addr: &str, auth_token: &str, game_port: u16, cancel: CancellationToken, ) -> Result<(), String> { let mut host_conn = TcpStream::connect(proxy_addr) .await .map_err(|e| format!("Proxy connect failed: {}", e))?; write_line(&mut host_conn, &format!("HOST {} 0", auth_token)).await?; let mut game_stream = tokio::time::timeout( std::time::Duration::from_secs(30), async { loop { match TcpStream::connect(format!("127.0.0.1:{}", game_port)).await { Ok(s) => return Ok::<_, String>(s), Err(_) => { tokio::select! { _ = cancel.cancelled() => return Err("Cancelled".into()), _ = tokio::time::sleep(std::time::Duration::from_secs(1)) => {} } } } } } ).await .map_err(|_| "Timed out waiting for game to start".to_string())??; let client_line = read_line(&mut host_conn).await?; let client_parts: Vec<&str> = client_line.split_whitespace().collect(); if client_parts.len() < 2 || client_parts[0] != "CLIENT" { return Err(format!("Expected CLIENT, got: {}", client_line)); } let joiner_id = client_parts[1]; let mut accept_conn = TcpStream::connect(proxy_addr) .await .map_err(|e| format!("Proxy connect failed: {}", e))?; write_line(&mut accept_conn, &format!("ACCEPT {} 0 {}", auth_token, joiner_id)).await?; tokio::select! { res = tokio::io::copy_bidirectional(&mut game_stream, &mut accept_conn) => { if let Err(e) = res { eprintln!("Host Relay error: {}", e); } }, _ = cancel.cancelled() => {}, } Ok(()) } async fn run_relay_proxy( proxy_state: &ProxyGuard, proxy_addr: &str, auth_token: &str, target_session: &str, cancel: CancellationToken, ) -> Result { let mut stream = TcpStream::connect(proxy_addr) .await .map_err(|e| format!("Proxy connect failed: {}", e))?; write_line(&mut stream, &format!("JOIN {} 0 {}", auth_token, target_session)).await?; let listener = TcpListener::bind("0.0.0.0:61000") .await .map_err(|e| format!("Bind failed: {}", e))?; let local_port = listener.local_addr().map_err(|e| e.to_string())?.port(); { let mut port = proxy_state.local_port.lock().await; *port = Some(local_port); } let (mut local_stream, _) = tokio::select! { r = listener.accept() => r.map_err(|e| format!("Accept failed: {}", e))?, _ = cancel.cancelled() => return Err("Cancelled".into()), }; tokio::select! { res = tokio::io::copy_bidirectional(&mut local_stream, &mut stream) => { if let Err(e) = res { eprintln!("Relay Proxy error: {}", e); } }, _ = cancel.cancelled() => {}, } Ok(local_port) } #[tauri::command] pub async fn start_host_relay( proxy_state: State<'_, ProxyGuard>, auth_token: String, game_port: u16, ) -> Result<(), String> { let addr = PROXY_ADDR; let cancel = CancellationToken::new(); let session_id = "__host__".to_string(); { let mut tokens = proxy_state.cancel_tokens.lock().await; if let Some(old) = tokens.insert(session_id.clone(), cancel.clone()) { old.cancel(); } } let result = run_host_relay(&proxy_state, &addr, &auth_token, game_port, cancel).await; { let mut tokens = proxy_state.cancel_tokens.lock().await; tokens.remove(&session_id); } result } #[tauri::command] pub async fn start_relay_proxy( proxy_state: State<'_, ProxyGuard>, auth_token: String, target_session: String, ) -> Result { let addr = PROXY_ADDR; let cancel = CancellationToken::new(); let session_id = target_session.clone(); { let mut tokens = proxy_state.cancel_tokens.lock().await; tokens.insert(session_id.clone(), cancel.clone()); } let local_port = run_relay_proxy(&proxy_state, &addr, &auth_token, &target_session, cancel).await?; { let mut tokens = proxy_state.cancel_tokens.lock().await; tokens.remove(&session_id); } Ok(local_port) } #[tauri::command] pub async fn stop_proxy(proxy_state: State<'_, ProxyGuard>, session_id: String) -> Result<(), String> { let mut tokens = proxy_state.cancel_tokens.lock().await; let found = tokens.remove(&session_id); if let Some(token) = found { token.cancel(); let mut port = proxy_state.local_port.lock().await; *port = None; } Ok(()) } #[tauri::command] pub async fn stop_all_proxies(proxy_state: State<'_, ProxyGuard>) -> Result<(), String> { let mut tokens = proxy_state.cancel_tokens.lock().await; for (_, token) in tokens.drain() { token.cancel(); } let mut port = proxy_state.local_port.lock().await; *port = None; Ok(()) } #[tauri::command] pub async fn join_game( app: tauri::AppHandle, game_state: State<'_, crate::state::GameState>, _proxy_state: State<'_, ProxyGuard>, _api_base_url: String, _auth_token: String, host_ip: String, host_port: u16, _target_session: String, instance_id: String, ) -> Result<(), String> { let server = crate::types::McServer { name: host_ip.clone(), ip: host_ip, port: host_port, }; #[cfg(target_os = "windows")] { let deadline = tokio::time::Instant::now() + tokio::time::Duration::from_secs(10); loop { if tokio::net::TcpStream::connect(format!("127.0.0.1:{}", host_port)).await.is_ok() { break; } if tokio::time::Instant::now() >= deadline { return Err("Timed out waiting for relay proxy".into()); } tokio::time::sleep(std::time::Duration::from_millis(200)).await; } } //neo: workaround for Windows having a race condition where the game is launched before the relay proxy crate::commands::game::launch_game(app, game_state, instance_id, vec![server], vec![]).await }