LCE-Emerald-Launcher/src-tauri/src/networking/relay.rs
neo 3 b611c97aee
Some checks are pending
Nix / build (push) Waiting to run
merge dev into main (#197)
Co-authored-by: str1k3r <115313679+S1l3ntStr1ke87@users.noreply.github.com>
Co-authored-by: ByteBukkit <codeddev21@gmail.com>
2026-10-10 13:03:33 +03:00

215 lines
7 KiB
Rust

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<String, String> {
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<u16, String> {
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<u16, String> {
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
}