1pub mod devices;
8pub mod dsu;
9pub mod errors;
10pub mod reader;
11pub mod server;
12
13pub use server::ServerConfig;
14
15use std::sync::Arc;
16use std::sync::atomic;
17use std::time::Duration;
18
19use crate::devices::{Device, DeviceFamily};
20use crate::errors::{DeviceError, ServerError};
21
22pub(crate) const READ_ATOMIC_BOOL_ORDERING: atomic::Ordering = atomic::Ordering::Relaxed;
23const CONTROLLER_OPEN_RETRY_DELAY_SEC: u64 = 5;
24
25pub fn run_server(
36 running: Arc<atomic::AtomicBool>,
37 config: server::ServerConfig,
38 device_config: devices::DeviceConfig,
39 family: DeviceFamily,
40) -> Result<(), ServerError> {
41 let mut api = hidapi::HidApi::new()?;
42
43 loop {
44 if !running.load(READ_ATOMIC_BOOL_ORDERING) {
45 return Ok(());
46 }
47
48 if let Err(e) = api.refresh_devices() {
49 log::warn!("Failed to refresh HID device list: {e}");
50 }
51
52 let Some(device) = open_controller_with_retry(
53 running.clone(),
54 &mut api,
55 device_config.clone(),
56 config.device_path.as_deref(),
57 family,
58 ) else {
59 return Ok(());
61 };
62
63 log::info!("Controller opened. Initializing...");
64 if let Err(e) = device.initialize() {
65 log::error!("Failed to initialize device: {e}");
66 sleep_interruptible(&running, Duration::from_secs(3));
67 continue;
68 }
69 log::info!(
70 "Device initialized. Starting CemuHook server on {}:{} ...",
71 config.bind_addr,
72 config.port
73 );
74
75 let (reader_handle, rx) = reader::spawn_reader(running.clone(), device);
79
80 let udp_server = server::Server::new(running.clone(), config.clone())?;
81
82 match udp_server.run(rx) {
83 Ok((recv_result, send_result)) => {
84 if let Err(e) = recv_result {
85 log::error!("UDP server receive loop error: {e}");
86 }
87 if let Err(err) = send_result {
88 log::error!("UDP server send thread panicked: {err:?}");
89 }
90 }
91 Err(e) => {
92 log::error!("Failed to start the UDP server: {e}");
93 }
94 }
95
96 if let Err(err) = reader_handle.join() {
97 log::error!("Reader thread panicked: {err:?}");
98 }
99
100 if !running.load(READ_ATOMIC_BOOL_ORDERING) {
101 return Ok(());
102 }
103
104 log::info!("Server shut down. Waiting 3 seconds before reconnect...");
105 sleep_interruptible(&running, Duration::from_secs(3));
106 }
107}
108
109pub fn run_debug_dump(
118 running: Arc<atomic::AtomicBool>,
119 device_path: Option<&str>,
120 device_config: Option<devices::DeviceConfig>,
121 family: DeviceFamily,
122) -> Result<(), DeviceError> {
123 let api = hidapi::HidApi::new()?;
124 let device_config = device_config.unwrap_or_default();
125
126 let device = open_device(family, device_config, &api, device_path)?;
127
128 log::info!("Controller opened. Running initialization...");
129 device.initialize()?;
130 log::info!("Initialized. Dumping frames...");
131
132 let (reader_handle, rx) = reader::spawn_reader(running.clone(), device);
133
134 while running.load(READ_ATOMIC_BOOL_ORDERING) {
135 match rx.recv() {
136 Ok(frame) => {
137 let buttons_pressed: Vec<&str> = [
138 ("A", frame.a),
139 ("B", frame.b),
140 ("X", frame.x),
141 ("Y", frame.y),
142 ("L1", frame.l1),
143 ("R1", frame.r1),
144 ("L2", frame.l2),
145 ("R2", frame.r2),
146 ("L3", frame.l3),
147 ("R3", frame.r3),
148 ("Options", frame.options),
149 ("Share", frame.share),
150 ("Home", frame.home),
151 ("QAM", frame.touch),
152 ]
153 .iter()
154 .filter(|(_, p)| *p)
155 .map(|(n, _)| *n)
156 .collect();
157
158 let dpad_pressed: Vec<&str> = [
159 ("Up", frame.dpad_up),
160 ("Down", frame.dpad_down),
161 ("Left", frame.dpad_left),
162 ("Right", frame.dpad_right),
163 ]
164 .iter()
165 .filter(|(_, p)| *p)
166 .map(|(n, _)| *n)
167 .collect();
168
169 let buttons_str = if buttons_pressed.is_empty() {
170 "none".to_string()
171 } else {
172 buttons_pressed.join(" ")
173 };
174 let dpad_str = if dpad_pressed.is_empty() {
175 "none".to_string()
176 } else {
177 dpad_pressed.join(" ")
178 };
179
180 println!(
181 "Buttons: {buttons_str}\n\
182 DPad: {dpad_str}\n\
183 Sticks: L({:4},{:4}) R({:4},{:4})\n\
184 Triggers: L2={:3} R2={:3}\n\
185 Accel: ({:7.3},{:7.3},{:7.3}) g\n\
186 Gyro: ({:8.1},{:8.1},{:8.1}) dps",
187 frame.left_stick_x,
188 frame.left_stick_y,
189 frame.right_stick_x,
190 frame.right_stick_y,
191 frame.analog_l2,
192 frame.analog_r2,
193 frame.accel_x,
194 frame.accel_y,
195 frame.accel_z,
196 frame.gyro_x,
197 frame.gyro_y,
198 frame.gyro_z
199 );
200 }
201 Err(e) => {
202 log::error!("Recv error: {e}");
203 break;
204 }
205 }
206 }
207
208 drop(rx);
209 if let Err(err) = reader_handle.join() {
210 log::error!("Reader thread panicked: {err:?}");
211 }
212
213 log::info!("Debug dump finished.");
214 Ok(())
215}
216
217fn open_controller_with_retry(
221 running: Arc<atomic::AtomicBool>,
222 api: &mut hidapi::HidApi,
223 device_config: devices::DeviceConfig,
224 device_path: Option<&str>,
225 family: DeviceFamily,
226) -> Option<Box<dyn devices::Device + Send>> {
227 loop {
228 if !running.load(READ_ATOMIC_BOOL_ORDERING) {
229 return None;
230 }
231
232 if let Err(err) = api.refresh_devices() {
234 log::error!("HidApi failed to refresh_devices: {err:?}");
235 return None;
236 }
237
238 match open_device(family, device_config.clone(), api, device_path) {
239 Ok(d) => return Some(d),
240 Err(e) => {
241 log::warn!(
242 "Failed to open controller: {e}. Retrying in {} seconds...",
243 CONTROLLER_OPEN_RETRY_DELAY_SEC
244 );
245 sleep_interruptible(
246 &running,
247 Duration::from_secs(CONTROLLER_OPEN_RETRY_DELAY_SEC),
248 );
249 }
250 }
251 }
252}
253
254fn open_device(
255 family: DeviceFamily,
256 config: devices::DeviceConfig,
257 api: &hidapi::HidApi,
258 device_path: Option<&str>,
259) -> Result<Box<dyn devices::Device + Send>, DeviceError> {
260 match family {
261 DeviceFamily::Triton => Ok(Box::new(devices::triton::Triton::find(
262 config,
263 api,
264 device_path,
265 )?)),
266 DeviceFamily::Legacy => Ok(Box::new(devices::legacy::LegacySteamController::find(
267 config,
268 api,
269 device_path,
270 )?)),
271 }
272}
273
274pub(crate) fn sleep_interruptible(running: &atomic::AtomicBool, total: Duration) {
276 let start = std::time::Instant::now();
277 while start.elapsed() < total {
278 if !running.load(READ_ATOMIC_BOOL_ORDERING) {
279 return;
280 }
281 std::thread::sleep(Duration::from_millis(100).min(total - start.elapsed()));
282 }
283}