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