diff --git a/elapseio/.gitignore b/elapseio/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/elapseio/.gitignore @@ -0,0 +1 @@ +/target diff --git a/elapseio/Cargo.lock b/elapseio/Cargo.lock new file mode 100644 index 0000000..8240035 --- /dev/null +++ b/elapseio/Cargo.lock @@ -0,0 +1,156 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "mercuryio" +version = "0.1.0" +dependencies = [ + "windows", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[package]] +name = "windows" +version = "0.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9579d0e6970fd5250aa29aba5994052385ff55cf7b28a059e484bb79ea842e42" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90dd7a7b86859ec4cdf864658b311545ef19dbcf17a672b52ab7cefe80c336f" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57fe7168f7de578d2d8a05b07fd61870d2e73b4020e9f49aa00da8471723497c" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2194dee901458cb79e1148a4e9aac2b164cc95fa431891e7b296ff0b2f1d8a6" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + +[[package]] +name = "windows-numerics" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ce3498fe0aba81e62e477408383196b4b0363db5e0c27646f932676283b43d8" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-threading" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab47f085ad6932defa48855254c758cdd0e2f2d48e62a34118a268d8f345e118" +dependencies = [ + "windows-link", +] diff --git a/elapseio/Cargo.toml b/elapseio/Cargo.toml new file mode 100644 index 0000000..1dcb678 --- /dev/null +++ b/elapseio/Cargo.toml @@ -0,0 +1,13 @@ +cargo-features = ["per-package-target"] + +[package] +name = "elapseio" +version = "0.1.0" +edition = "2024" +default-target="x86_64-pc-windows-gnu" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +windows = { version = "0.62.0", features = ["Win32_Foundation", "Win32_UI_WindowsAndMessaging", "Win32_System_WindowsProgramming", "Win32_UI_Input_KeyboardAndMouse", "Win32_System_Diagnostics_Debug", "Win32_System_Threading", "Win32_UI_Input_Touch", "Win32_UI_Controls", "Win32_Graphics_Gdi", "Win32_Devices_Communication", "Win32_Storage_FileSystem", "Win32_Security", "Win32_System_Diagnostics_ToolHelp"] } \ No newline at end of file diff --git a/elapseio/rust-toolchain.toml b/elapseio/rust-toolchain.toml new file mode 100644 index 0000000..271800c --- /dev/null +++ b/elapseio/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" \ No newline at end of file diff --git a/elapseio/src/lib.rs b/elapseio/src/lib.rs new file mode 100644 index 0000000..de5c128 --- /dev/null +++ b/elapseio/src/lib.rs @@ -0,0 +1,204 @@ +#![feature(slice_as_array)] +use std::ffi::c_void; +use std::net::{SocketAddr, UdpSocket}; +use std::ptr::{self, NonNull}; +use std::sync::{Mutex, OnceLock}; +use std::thread::{self, JoinHandle}; +use std::time::{Duration, Instant}; + +use windows::Win32::Foundation::*; +use windows::core::*; + +static SOCKET: OnceLock = OnceLock::new(); +static LEDSOCKET: OnceLock = OnceLock::new(); + +#[unsafe(no_mangle)] +pub extern "system" fn mercury_io_get_api_version() -> u16 { + 0x100 +} + +#[unsafe(no_mangle)] +pub extern "system" fn mercury_io_init() -> HRESULT { + let s = UdpSocket::bind("127.0.0.1:45052").unwrap(); + let _ = s.set_read_timeout(Some(Duration::from_millis(8))); + let _ = SOCKET.set(s); + + let _ = LEDSOCKET.set(UdpSocket::bind("0.0.0.0:0").unwrap()); + + S_OK +} + +enum OpBtn { + Test = 0x01, + Service = 0x02, + Coin = 0x04, +} + +enum GameBtn { + VolUp = 0x01, + VolDown = 0x02, +} + +fn interleave_slices(input: &[bool], group_size: usize) -> Vec { + let num_groups = input.len() / group_size; + let mut result = Vec::with_capacity(input.len()); + + for i in 0..group_size { + for j in 0..num_groups { + let index = j * group_size + i; + result.push(input[index]); + } + } + + result +} + +static OP_BTN_LOCK: Mutex = Mutex::new(0); +static GAME_BTN_LOCK: Mutex = Mutex::new(0); + +#[unsafe(no_mangle)] +pub extern "system" fn mercury_io_poll() -> HRESULT { + let mut op_btn: u8 = 0; + let mut game_btn: u8 = 0; + + *OP_BTN_LOCK.lock().unwrap() = op_btn; + *GAME_BTN_LOCK.lock().unwrap() = game_btn; + + S_OK +} + +#[unsafe(no_mangle)] +pub extern "system" fn mercury_io_get_opbtns(opbtn: *mut u8) { + if let Some(mut ptr) = NonNull::new(opbtn) { + unsafe { + // Safety: relies on parent hook developer + let op_btn_ref = ptr.as_mut(); + *op_btn_ref = *OP_BTN_LOCK.lock().unwrap(); + } + } +} + +#[unsafe(no_mangle)] +pub extern "system" fn mercury_io_get_gamebtns(gamebtn: *mut u8) { + if let Some(mut ptr) = NonNull::new(gamebtn) { + unsafe { + // Safety: relies on parent hook developer + let game_btn_ref = ptr.as_mut(); + *game_btn_ref = *GAME_BTN_LOCK.lock().unwrap(); + } + } +} + +#[unsafe(no_mangle)] +pub extern "system" fn mercury_io_touch_init() -> HRESULT { + S_OK +} + +#[unsafe(no_mangle)] +static _TOUCH_THREAD_HANDLE: Mutex>> = Mutex::new(None); + +#[unsafe(no_mangle)] +pub extern "system" fn mercury_io_touch_start(perform_scan: extern "C" fn(*mut bool)) { + if let Some(handle) = &*_TOUCH_THREAD_HANDLE.lock().unwrap() { + if !handle.is_finished() { + return; + } + } + let handle = std::thread::spawn(move || { + let mut buf = [0; 1500]; + loop { + let start = Instant::now(); + let mut cell_pressed: [bool; 240] = [false; 240]; + match SOCKET.get() { + Some(socket) => { + match socket.recv_from(&mut buf) { + Ok((amt, _src)) => { + let bytes = buf[..amt].to_vec(); + let bool_vec: Vec = bytes + .iter() + .flat_map(|&byte| { + (0..4).map(move |i| { + let bit1 = (byte >> (7 - i * 2)) & 1; + let bit2 = (byte >> (6 - i * 2)) & 1; + (bit1 | bit2) == 1 + }) + }) + .collect(); + // let boolleft: &[bool; 240] = bool_vec[120..240].as_array().unwrap(); + // let boolright: &[bool; 240] = bool_vec[..120]; + let mut bl = [false; 240]; + let mut br = [false; 240]; + //left side + let mut v: Vec = Vec::new(); + for x in bool_vec[..120].as_chunks::<4>().0.iter().rev() { + v.append(&mut x.to_vec()); + } + cell_pressed[120..240].copy_from_slice(&interleave_slices(&v, 4)); + //right side + cell_pressed[..120] + .copy_from_slice(&interleave_slices(&bool_vec[120..240], 4)); + // let c: [bool; _] = *bool_vec[..240].as_array().unwrap(); + } + Err(e) => (), + }; + } + None => { + println!("Socket hasn't been initialized yet") + } + } + if start.elapsed() < Duration::from_millis(8) { + let remaining_time = Duration::from_millis(8) - start.elapsed(); + thread::sleep(remaining_time); + } + perform_scan(cell_pressed.as_mut_ptr()); + } + }); + *_TOUCH_THREAD_HANDLE.lock().unwrap() = Some(handle); +} + +struct RGB { + red: u8, + green: u8, + blue: u8, +} + +impl IntoIterator for RGB { + type Item = u8; + type IntoIter = std::array::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + IntoIterator::into_iter([self.red, self.green, self.blue]) + } +} + +fn flatten(v: Vec>) -> Vec { + v.into_iter().flatten().flatten().collect() +} + +#[unsafe(no_mangle)] +pub extern "system" fn mercury_io_touch_set_leds(ledsaddr: usize) { + let mut header: Vec = vec![2, 2]; + let mut leds: Vec = Vec::new(); + for i in (3..1920).step_by(4) { + let n: u32 = unsafe { ptr::read((ledsaddr + i) as *const u32) }; + let a: [u8; 4] = n.to_le_bytes(); + leds.push(RGB { + red: a[1], + green: a[2], + blue: a[3], + }); + } + + let addr = SocketAddr::from(([127, 0, 0, 1], 21324)); + let mut flattened: Vec = leds.into_iter().flatten().collect(); + header.append(&mut flattened); + match SOCKET.get() { + Some(socket) => { + let sock: UdpSocket = socket.try_clone().unwrap(); + let _ = sock.send_to(&header, addr); + } + None => { + println!("Ledsocket hasn't been initialized yet") + } + } +} diff --git a/src/input/mod.rs b/src/input/mod.rs index d55c709..8995902 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -1,5 +1,6 @@ mod file; mod realtimeudp; mod stdin; +mod piston; pub struct Input; \ No newline at end of file diff --git a/src/input/piston.rs b/src/input/piston.rs new file mode 100644 index 0000000..afe3c6a --- /dev/null +++ b/src/input/piston.rs @@ -0,0 +1,30 @@ +use std::{net::UdpSocket, sync::mpsc::Sender, thread}; + +use crate::input::Input; + +impl Input { + pub fn piston(tx: Sender>) { + thread::spawn(move || { + let socket = UdpSocket::bind("127.0.0.1:45052").unwrap(); + let mut buf = [0; 1500]; + loop { + let mut leds: Vec<[f32; 4]> = Vec::new(); + println!("dead"); + let (amt, _src) = socket.recv_from(&mut buf).unwrap(); + let bytes = buf[..amt].to_vec(); + // let _ = tx.send(); + for byte in bytes { + for i in 0..8 { + let bit = (byte >> i) & 1; + if bit == 1 { + leds.push([1.0,1.0,1.0,1.0]) + } else { + leds.push([0.02,0.02,0.02,1.0]) + }; + } + } + let _ = tx.send(leds.clone()); + } + }); + } +} diff --git a/src/main.rs b/src/main.rs index 0256adb..8637ee5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,6 +26,8 @@ fn main() { let args = Args::parse(); let (tx, rx): (Sender>, Receiver>) = mpsc::channel(); match args.input.as_str() { + #[cfg(feature = "piston")] + "piston" => Input::piston(tx), "-" | "stdin" => Input::stdin(tx), _ => { match Url::parse(&args.input) { diff --git a/src/output/piston.rs b/src/output/piston.rs index baa41b9..b001f99 100644 --- a/src/output/piston.rs +++ b/src/output/piston.rs @@ -1,7 +1,10 @@ #![allow(non_camel_case_types)] -use piston_window::*; use piston_window::graphics::math::Scalar; +use piston_window::*; +use std::net::UdpSocket; +use std::sync::mpsc::{self, Sender}; +use std::time::Instant; use std::{sync::mpsc::Receiver, time::Duration}; use crate::output::Output; @@ -9,26 +12,157 @@ use crate::utils::{compare, offset}; impl Output { pub fn piston(rx: Receiver>) { - let mut window: PistonWindow = - WindowSettings::new("ELApse", [800, 800]) + let mut leds = vec![[0.02, 0.02, 0.02, 1.0]; 480]; + //Input init + let mut mousehold = false; + let mut lastpos: Option<(i16, i16)> = None; + let mut timesince = Instant::now(); + let mut buf = [0u8; 60]; + let socket = UdpSocket::bind("0.0.0.0:0").unwrap(); + + //screen init + let mut window: PistonWindow = WindowSettings::new("ELApse", [800, 800]) .transparent(true) - .exit_on_esc(true).build().unwrap(); + .exit_on_esc(true) + .build() + .unwrap(); while let Some(e) = window.next() { - let r = match rx.recv_timeout(Duration::from_millis(66)) { - Ok(e) => e, - Err(_) => vec![[0.02, 0.02, 0.02, 1.0]; 480], + //Input code + if let Some(pos) = e.mouse_cursor_args() { + let size = window.size(); + let wh = compare(size.height, size.width) * 0.75; + lastpos = calc_pos(pos, [size.width / 2.0, size.height / 2.0], wh); + } + if mousehold == true && lastpos.is_some() { + let mut n: usize = 0; + let mut bin = [0u8; 60]; + let mut touch = [false; 480]; + let l = lastpos.unwrap(); + for x in 0..60 { + for y in 0..8 { + if x == l.0 && y == l.1 { + touch[n] = true; + }; + n += 1; + } + } + let oldbuf = buf.clone(); + buf.copy_from_slice(&bitwise_or_arrays(&pack_bools_to_bytes(touch), &oldbuf).unwrap()); + if timesince.elapsed() >= Duration::from_millis(8) { + let _ = socket.send_to(&buf, "127.0.0.1:45052"); + buf = [0u8; 60]; + timesince = Instant::now() + } + } + if let Some(Button::Mouse(button)) = e.press_args() { + println!("Pressed mouse button '{:?}'", button); + if button == MouseButton::Left && lastpos.is_some() { + mousehold = true; + let mut n: usize = 0; + let mut bin = [0u8; 60]; + let mut touch = [false; 480]; + let l = lastpos.unwrap(); + for x in 0..60 { + for y in 0..8 { + if x == l.0 && y == l.1 { + touch[n] = true; + }; + n += 1; + } + } + let buf = pack_bools_to_bytes(touch); + let _ = socket.send_to(&buf, "127.0.0.1:45052"); + } + } + if let Some(Button::Mouse(button)) = e.release_args() { + if button == MouseButton::Left { + mousehold = false + } + } + + //Led receival + match rx.recv_timeout(Duration::from_millis(1)) { + Ok(e) => leds = e, + Err(_) => (), }; let size = window.size(); - let wh = compare(size.height, size.width)*0.75; + let wh = compare(size.height, size.width) * 0.75; window.draw_2d(&e, |c, g, _device| { for e in 1..9 { for i in 0..60 { - let rwh = wh*(1.0-(e as f64*0.06)); - circle_arc(r[i*8+(e-1)], rwh/32.0, Scalar::deg_to_rad(offset((((360/60)*i)) as i16, 360, 90) as f64), Scalar::deg_to_rad(offset((((360/60)*i)+6) as i16, 360, 90) as f64), [(size.width/2.0)-(rwh/2.0), (size.height/2.0)-(rwh/2.0), rwh, rwh], c.transform, g); + let rwh = wh * (1.0 - (e as f64 * 0.06)); + circle_arc( + leds[i * 8 + (e - 1)], + rwh / 32.0, + Scalar::deg_to_rad(offset(((360 / 60) * i) as i16, 360, 90) as f64), + Scalar::deg_to_rad( + offset((((360 / 60) * i) + 6) as i16, 360, 90) as f64 + ), + [ + (size.width / 2.0) - (rwh / 2.0), + (size.height / 2.0) - (rwh / 2.0), + rwh, + rwh, + ], + c.transform, + g, + ); } } }); } } } + +fn calc_pos(point: [f64; 2], center: [f64; 2], wh: f64) -> Option<(i16, i16)> { + let dx = point[0] - center[0]; + let dy = point[1] - center[1]; + let distance = (dx * dx + dy * dy).sqrt(); + + let angle = dy.atan2(dx); + let mut deg = angle * 30.0 / std::f64::consts::PI; + if deg < 0.0 { + deg += 60.0; + } + let x = offset(deg as i16, 60, 45)-1; + + let mut y: Option = None; + for i in 0..8 { + let rwh = wh * (1.0 - (i as f64 * 0.065)) / 2.0; + let rwe = wh * (1.0 - ((i as f64 + 1.0) * 0.065)) / 2.0; + if distance <= rwh && distance >= rwe { + y = Some(i) + } + } + // } + y.map(|y_val| (x, y_val)) +} + +fn pack_bools_to_bytes(bools: [bool; 480]) -> [u8; 60] { + let mut result = [0u8; 60]; + + for (i, &bit) in bools.iter().enumerate() { + let byte_index = i / 8; + let bit_index = i % 8; + + if bit { + result[byte_index] |= 1 << bit_index; + } + } + + result +} + +fn bitwise_or_arrays>(arr1: &[T], arr2: &[T]) -> Result, &'static str> { + if arr1.len() != arr2.len() { + return Err("Arrays must be the same length"); + } + + let result: Vec = arr1.iter() + .zip(arr2.iter()) + .map(|(&a, &b)| a | b) + .collect(); + + Ok(result) +} \ No newline at end of file