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