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