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;
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(
33 running: Arc<atomic::AtomicBool>,
34 config: server::ServerConfig,
35 device_config: devices::DeviceConfig,
36) -> Result<(), ServerError> {
37 let mut api = hidapi::HidApi::new()?;
38
39 loop {
40 if !running.load(READ_ATOMIC_BOOL_ORDERING) {
41 return Ok(());
42 }
43
44 if let Err(e) = api.refresh_devices() {
45 log::warn!("Failed to refresh HID device list: {e}");
46 }
47
48 let Some(device) = open_controller_with_retry(
49 running.clone(),
50 &mut api,
51 device_config.clone(),
52 config.device_path.as_deref(),
53 ) else {
54 return Ok(());
56 };
57
58 log::info!("Controller opened. Initializing...");
59 if let Err(e) = device.initialize() {
60 log::error!("Failed to initialize device: {e}");
61 sleep_interruptible(&running, Duration::from_secs(3));
62 continue;
63 }
64 log::info!(
65 "Device initialized. Starting CemuHook server on {}:{} ...",
66 config.bind_addr,
67 config.port
68 );
69
70 let (reader_handle, rx) = reader::spawn_reader(running.clone(), device);
74
75 let udp_server = server::Server::new(running.clone(), config.clone())?;
76
77 match udp_server.run(rx) {
78 Ok((recv_result, send_result)) => {
79 if let Err(e) = recv_result {
80 log::error!("UDP server receive loop error: {e}");
81 }
82 if let Err(err) = send_result {
83 log::error!("UDP server send thread panicked: {err:?}");
84 }
85 }
86 Err(e) => {
87 log::error!("Failed to start the UDP server: {e}");
88 }
89 }
90
91 if let Err(err) = reader_handle.join() {
92 log::error!("Reader thread panicked: {err:?}");
93 }
94
95 if !running.load(READ_ATOMIC_BOOL_ORDERING) {
96 return Ok(());
97 }
98
99 log::info!("Server shut down. Waiting 3 seconds before reconnect...");
100 sleep_interruptible(&running, Duration::from_secs(3));
101 }
102}
103
104pub fn run_debug_dump(
110 running: Arc<atomic::AtomicBool>,
111 device_path: Option<&str>,
112 device_config: Option<devices::DeviceConfig>,
113) -> Result<(), DeviceError> {
114 let api = hidapi::HidApi::new()?;
115 let device_config = device_config.unwrap_or_default();
116
117 let device = devices::triton::Triton::find(device_config, &api, device_path)?;
119
120 log::info!("Controller opened. Running initialization...");
121 device.initialize()?;
122 log::info!("Initialized. Dumping frames...");
123
124 let (reader_handle, rx) = reader::spawn_reader(running.clone(), device);
125
126 while running.load(READ_ATOMIC_BOOL_ORDERING) {
127 match rx.recv() {
128 Ok(frame) => {
129 let buttons_pressed: Vec<&str> = [
130 ("A", frame.a),
131 ("B", frame.b),
132 ("X", frame.x),
133 ("Y", frame.y),
134 ("L1", frame.l1),
135 ("R1", frame.r1),
136 ("L2", frame.l2),
137 ("R2", frame.r2),
138 ("L3", frame.l3),
139 ("R3", frame.r3),
140 ("Options", frame.options),
141 ("Share", frame.share),
142 ("Home", frame.home),
143 ("QAM", frame.touch),
144 ]
145 .iter()
146 .filter(|(_, p)| *p)
147 .map(|(n, _)| *n)
148 .collect();
149
150 let dpad_pressed: Vec<&str> = [
151 ("Up", frame.dpad_up),
152 ("Down", frame.dpad_down),
153 ("Left", frame.dpad_left),
154 ("Right", frame.dpad_right),
155 ]
156 .iter()
157 .filter(|(_, p)| *p)
158 .map(|(n, _)| *n)
159 .collect();
160
161 let buttons_str = if buttons_pressed.is_empty() {
162 "none".to_string()
163 } else {
164 buttons_pressed.join(" ")
165 };
166 let dpad_str = if dpad_pressed.is_empty() {
167 "none".to_string()
168 } else {
169 dpad_pressed.join(" ")
170 };
171
172 println!(
173 "Buttons: {buttons_str}\n\
174 DPad: {dpad_str}\n\
175 Sticks: L({:4},{:4}) R({:4},{:4})\n\
176 Triggers: L2={:3} R2={:3}\n\
177 Accel: ({:7.3},{:7.3},{:7.3}) g\n\
178 Gyro: ({:8.1},{:8.1},{:8.1}) dps",
179 frame.left_stick_x,
180 frame.left_stick_y,
181 frame.right_stick_x,
182 frame.right_stick_y,
183 frame.analog_l2,
184 frame.analog_r2,
185 frame.accel_x,
186 frame.accel_y,
187 frame.accel_z,
188 frame.gyro_x,
189 frame.gyro_y,
190 frame.gyro_z
191 );
192 }
193 Err(e) => {
194 log::error!("Recv error: {e}");
195 break;
196 }
197 }
198 }
199
200 drop(rx);
201 if let Err(err) = reader_handle.join() {
202 log::error!("Reader thread panicked: {err:?}");
203 }
204
205 log::info!("Debug dump finished.");
206 Ok(())
207}
208
209fn open_controller_with_retry(
212 running: Arc<atomic::AtomicBool>,
213 api: &mut hidapi::HidApi,
214 device_config: devices::DeviceConfig,
215 device_path: Option<&str>,
216) -> Option<impl devices::Device + use<>> {
217 loop {
218 if !running.load(READ_ATOMIC_BOOL_ORDERING) {
219 return None;
220 }
221
222 if let Err(err) = api.refresh_devices() {
226 log::error!("HidApi failed to refresh_devices: {err:?}");
227 return None;
228 }
229
230 match devices::triton::Triton::find(device_config.clone(), api, device_path) {
231 Ok(d) => return Some(d),
232 Err(e) => {
233 log::warn!(
234 "Failed to open controller: {e}. Retrying in {} seconds...",
235 CONTROLLER_OPEN_RETRY_DELAY_SEC
236 );
237 sleep_interruptible(
238 &running,
239 Duration::from_secs(CONTROLLER_OPEN_RETRY_DELAY_SEC),
240 );
241 }
242 }
243 }
244}
245
246pub(crate) fn sleep_interruptible(running: &atomic::AtomicBool, total: Duration) {
248 let start = std::time::Instant::now();
249 while start.elapsed() < total {
250 if !running.load(READ_ATOMIC_BOOL_ORDERING) {
251 return;
252 }
253 std::thread::sleep(Duration::from_millis(100).min(total - start.elapsed()));
254 }
255}