use std::net::{SocketAddr, TcpListener, TcpStream};
use yog_hot_reload::control::{self, Command, Event, ModInfo};
pub fn start_if_requested() {
let (Ok(addr), Ok(token)) = (
std::env::var(yog_ids::env::CONTROL_SOCKET),
std::env::var(yog_ids::env::CONTROL_TOKEN),
) else {
return;
};
let addr: SocketAddr = match addr.parse() {
Ok(a) => a,
Err(e) => {
yog_logging::error!("control socket: `{addr}` isn't an address ({e})");
return;
}
};
if !addr.ip().is_loopback() {
yog_logging::error!("control socket: {addr} isn't a loopback address — not listening");
return;
}
let listener = match TcpListener::bind(addr) {
Ok(l) => l,
Err(e) => {
yog_logging::error!("control socket: failed to bind {addr}: {e}");
return;
}
};
std::thread::spawn(move || {
for stream in listener.incoming().flatten() {
handle_client(stream, &token);
}
});
}
#[cfg(target_os = "linux")]
fn allow_to_trace(pid: u32) -> bool {
if pid == 0 {
return false;
}
let rc = unsafe { libc::prctl(libc::PR_SET_PTRACER, pid as libc::c_ulong, 0, 0, 0) };
if rc == 0 {
return true;
}
let err = std::io::Error::last_os_error();
if err.raw_os_error() == Some(libc::EINVAL) {
return true;
}
yog_logging::warn!("control socket: PR_SET_PTRACER({pid}) failed: {err}");
false
}
fn send(stream: &mut TcpStream, event: &Event) {
let _ = control::send(stream, event);
}
fn mod_list() -> Vec<ModInfo> {
crate::globals::MOD_INFOS
.lock()
.iter()
.map(|m| ModInfo {
id: m.id.clone(),
name: m.name.clone(),
version: m.version.clone(),
})
.collect()
}
fn handle_client(mut stream: TcpStream, token: &str) {
let first = control::receive(&mut stream).ok().flatten();
if !greet(&mut stream, first, token) {
return;
}
while let Ok(Some(cmd)) = control::receive(&mut stream) {
answer(&mut stream, cmd);
}
}
fn greet(stream: &mut TcpStream, first: Option<Command>, token: &str) -> bool {
let Some(Command::Hello { token: theirs, pid }) = first else {
yog_logging::warn!("control socket: a client that didn't say hello — closed");
return false;
};
if theirs != token {
yog_logging::warn!("control socket: a client without the token — closed");
return false;
}
#[cfg(target_os = "linux")]
let ptracer = allow_to_trace(pid);
#[cfg(not(target_os = "linux"))]
let ptracer = {
let _ = pid;
false
};
send(
stream,
&Event::Ready {
pid: std::process::id(),
mods: mod_list(),
ptracer,
},
);
true
}
fn answer(stream: &mut TcpStream, cmd: Command) {
let reply = match cmd {
Command::HotReload {
mod_id,
yog_path,
resources,
} => {
let ok = crate::mods::hot_reload::trigger_hot_reload(&mod_id, &yog_path);
if ok && resources {
crate::mods::registrations::reload_game_resources();
}
Event::HotReloadDone { mod_id, ok }
}
Command::HotReloadCheck { mod_id, yog_path } => {
let changes = crate::mods::registrations::changes(&mod_id, &yog_path);
Event::HotReloadCheck { mod_id, changes }
}
Command::ReloadResources => {
crate::mods::registrations::reload_game_resources();
Event::ResourcesReloaded
}
Command::ListMods => Event::Mods { mods: mod_list() },
Command::Hello { .. } => return,
};
send(stream, &reply);
}