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