Files
emod-cli/src/debug/process.rs

265 lines
7.5 KiB
Rust
Raw Normal View History

use std::{io, path::Path, thread};
use crate::error::{CliError, Result};
use super::{
config::{DebugConfig, ResolvedModDir},
hotreload::{HotReloadOptions, HotReloadTask},
ipc::DebugIpcServer,
log::{self, LineBuffer},
};
#[cfg(windows)]
use std::{ffi::OsStr, os::windows::ffi::OsStrExt, ptr};
#[cfg(windows)]
use windows_sys::Win32::{
Foundation::{
CloseHandle, ERROR_BROKEN_PIPE, GetLastError, HANDLE, HANDLE_FLAG_INHERIT,
SetHandleInformation,
},
Storage::FileSystem::ReadFile,
System::{
Console::{GetStdHandle, STD_INPUT_HANDLE},
Pipes::CreatePipe,
Threading::{
CREATE_UNICODE_ENVIRONMENT, CreateProcessW, INFINITE, PROCESS_INFORMATION,
STARTF_USESTDHANDLES, STARTUPINFOW, WaitForSingleObject,
},
},
};
#[cfg(windows)]
use windows_sys::Win32::Security::SECURITY_ATTRIBUTES;
#[cfg(windows)]
use super::win::WinHandle;
#[cfg(windows)]
pub fn launch_game(
config: &DebugConfig,
config_arg: Option<&Path>,
mod_dirs: &[ResolvedModDir],
) -> Result<()> {
let enable_ipc = config.auto_hot_reload_mods;
let ipc = if enable_ipc {
let server = DebugIpcServer::start()?;
println!("[MCDK] IPC调试服务器已启动端口{}", server.port());
Some(server)
} else {
None
};
let command = build_command(config, config_arg);
let mut command_w = wide_null(OsStr::new(&command));
let env_block = if ipc.is_some() || config.auto_hot_reload_ui {
Some(build_environment_block(
ipc.as_ref().map(|server| server.port()),
))
} else {
None
};
let mut security = SECURITY_ATTRIBUTES {
nLength: std::mem::size_of::<SECURITY_ATTRIBUTES>() as u32,
lpSecurityDescriptor: ptr::null_mut(),
bInheritHandle: 1,
};
let (out_read, out_write) = create_pipe_pair(&mut security)?;
let (err_read, err_write) = create_pipe_pair(&mut security)?;
let mut startup: STARTUPINFOW = unsafe { std::mem::zeroed() };
startup.cb = std::mem::size_of::<STARTUPINFOW>() as u32;
startup.dwFlags = STARTF_USESTDHANDLES;
startup.hStdOutput = out_write.raw();
startup.hStdError = err_write.raw();
startup.hStdInput = unsafe { GetStdHandle(STD_INPUT_HANDLE) };
let mut process_info: PROCESS_INFORMATION = unsafe { std::mem::zeroed() };
let env_ptr = env_block
.as_ref()
.map(|block| block.as_ptr().cast_mut().cast())
.unwrap_or(ptr::null_mut());
let creation_flags = if env_block.is_some() {
CREATE_UNICODE_ENVIRONMENT
} else {
0
};
let ok = unsafe {
CreateProcessW(
ptr::null(),
command_w.as_mut_ptr(),
ptr::null_mut(),
ptr::null_mut(),
1,
creation_flags,
env_ptr,
ptr::null(),
&startup,
&mut process_info,
)
};
if ok == 0 {
let err = io::Error::last_os_error();
return Err(CliError::Io(io::Error::new(
err.kind(),
format!("CreateProcessW failed for command {command:?}: {err}"),
)));
}
drop(out_write);
drop(err_write);
let pid = process_info.dwProcessId;
let stdout_filter = config.include_debug_mod;
let stderr_filter = config.include_debug_mod;
let stdout_thread =
thread::spawn(move || read_pipe(out_read, stdout_filter, log::process_stdout_line));
let stderr_thread =
thread::spawn(move || read_pipe(err_read, stderr_filter, log::process_stderr_line));
let mut hotreload = HotReloadTask::start(
pid,
mod_dirs,
ipc.clone(),
HotReloadOptions {
python: config.auto_hot_reload_mods,
json_ui: config.auto_hot_reload_ui,
},
);
unsafe { WaitForSingleObject(process_info.hProcess, INFINITE) };
if let Some(task) = &mut hotreload {
task.safe_exit();
}
if let Some(server) = &ipc {
server.safe_exit();
}
let _ = stdout_thread.join();
let _ = stderr_thread.join();
unsafe {
CloseHandle(process_info.hProcess);
CloseHandle(process_info.hThread);
}
Ok(())
}
#[cfg(not(windows))]
pub fn launch_game(
_config: &DebugConfig,
_config_arg: Option<&Path>,
_mod_dirs: &[ResolvedModDir],
) -> Result<()> {
Err(CliError::InvalidInput(
"debug launch is only supported on Windows".to_string(),
))
}
#[cfg(windows)]
fn build_command(config: &DebugConfig, config_arg: Option<&Path>) -> String {
let mut command = String::new();
command.push('"');
command.push_str(&config.game_executable_path);
command.push('"');
if !config.netease_config.chat_extension {
command.push_str(" chatExtension=false");
}
if let Some(path) = config_arg {
command.push_str(" config=\"");
command.push_str(&path.to_string_lossy().replace('\\', "/"));
command.push('"');
}
command
}
#[cfg(windows)]
fn create_pipe_pair(security: &mut SECURITY_ATTRIBUTES) -> Result<(WinHandle, WinHandle)> {
let mut read: HANDLE = ptr::null_mut();
let mut write: HANDLE = ptr::null_mut();
let ok = unsafe { CreatePipe(&mut read, &mut write, security, 0) };
if ok == 0 {
return Err(CliError::Io(io::Error::last_os_error()));
}
let read = WinHandle::new(read)?;
let write = WinHandle::new(write)?;
let ok = unsafe { SetHandleInformation(read.raw(), HANDLE_FLAG_INHERIT, 0) };
if ok == 0 {
return Err(CliError::Io(io::Error::last_os_error()));
}
Ok((read, write))
}
#[cfg(windows)]
fn read_pipe<F>(pipe: WinHandle, filter_python: bool, process_line: F)
where
F: Fn(&str) + Send + 'static,
{
const BUFSZ: usize = 4096;
let mut line_buffer = LineBuffer::new(filter_python);
let mut buffer = [0u8; BUFSZ];
loop {
let mut bytes_read = 0u32;
let ok = unsafe {
ReadFile(
pipe.raw(),
buffer.as_mut_ptr().cast(),
buffer.len() as u32,
&mut bytes_read,
ptr::null_mut(),
)
};
if ok == 0 {
let err = unsafe { GetLastError() };
if err == ERROR_BROKEN_PIPE {
line_buffer.flush(|line| process_line(&line));
}
break;
}
if bytes_read == 0 {
line_buffer.flush(|line| process_line(&line));
break;
}
line_buffer.append(&buffer[..bytes_read as usize], |line| process_line(&line));
}
}
#[cfg(windows)]
fn build_environment_block(ipc_port: Option<u16>) -> Vec<u16> {
let mut pairs: Vec<(Vec<u16>, Vec<u16>)> = std::env::vars_os()
.filter(|(key, _)| {
let key = key.to_string_lossy();
!key.eq_ignore_ascii_case("MCDEV_DEBUG_IPC_PORT")
})
.map(|(key, value)| (key.encode_wide().collect(), value.encode_wide().collect()))
.collect();
if let Some(port) = ipc_port {
pairs.push((
OsStr::new("MCDEV_DEBUG_IPC_PORT").encode_wide().collect(),
OsStr::new(&port.to_string()).encode_wide().collect(),
));
}
pairs.sort_by(|a, b| a.0.cmp(&b.0));
let mut block = Vec::new();
for (key, value) in pairs {
block.extend(key);
block.push('=' as u16);
block.extend(value);
block.push(0);
}
block.push(0);
block
}
#[cfg(windows)]
fn wide_null(value: &OsStr) -> Vec<u16> {
value.encode_wide().chain(std::iter::once(0)).collect()
}