pine_ipc/
lib.rs

1use std::{
2    env,
3    io::{Cursor, Read, Write},
4    net::{Ipv4Addr, SocketAddrV4, TcpStream},
5    path::{Path, PathBuf},
6    str::Utf8Error,
7    sync::Mutex,
8};
9#[cfg(target_family = "unix")] use std::os::unix::net::UnixStream;
10use thiserror::Error;
11
12#[derive(Error, Debug)]
13pub enum PINEError {
14    #[error("IO error: {0}")]
15    IO(#[from] std::io::Error),
16
17    #[error("UTF-8 error: {0}")]
18    UTF8(#[from] Utf8Error),
19
20    #[error("Command returned a non-zero response code")]
21    CommandFailure,
22
23    #[cfg(target_family = "unix")]
24    #[error("Unsupported operating system")]
25    UnsupportedOS,
26
27    #[cfg(target_family = "unix")]
28    #[error("Unix socket not found: {0}")]
29    UnixSocket(PathBuf),
30}
31
32pub type PINEResult<T> = Result<T, PINEError>;
33
34pub struct PINE<T: Read + Write> {
35    stream: T,
36    mutex: Mutex<()>,
37}
38
39impl<T: Read + Write> PINE<T> {
40    pub fn from_stream(stream: T) -> Self {
41        let mutex = Mutex::new(());
42        Self { stream, mutex }
43    }
44
45    pub fn into_inner(self) -> T {
46        self.stream
47    }
48
49    pub fn send_raw(&mut self, buffer: &[u8]) -> PINEResult<Vec<u8>> {
50        // Acquire lock
51        let _unused = self.mutex.lock().unwrap();
52
53        // Write buffer to socket
54        self.stream.write_all(buffer)?;
55
56        // Read response header
57        let res_size = read_u32(&mut self.stream)?;
58        let res_result = read_u8(&mut self.stream)?;
59        if res_result != 0 {
60            return Err(PINEError::CommandFailure);
61        }
62
63        // Read buffer
64        let mut res_buffer = vec![0; res_size as usize - 5];
65        self.stream.read_exact(res_buffer.as_mut_slice())?;
66        Ok(res_buffer)
67    }
68
69    pub fn send(&mut self, batch: &mut PINEBatch) -> PINEResult<Vec<PINEResponse>> {
70        let buffer = batch.finalize();
71        let res_buffer = self.send_raw(buffer)?;
72
73        // Parse responses
74        let mut res = Vec::<PINEResponse>::with_capacity(batch.commands.len());
75        let reader = &mut Cursor::new(res_buffer);
76        for command in batch.commands.iter() {
77            res.push(match command {
78                PINECommand::MsgRead8 { .. } => PINEResponse::ResRead8 {
79                    val: read_u8(reader)?,
80                },
81                PINECommand::MsgRead16 { .. } => PINEResponse::ResRead16 {
82                    val: read_u16(reader)?,
83                },
84                PINECommand::MsgRead32 { .. } => PINEResponse::ResRead32 {
85                    val: read_u32(reader)?,
86                },
87                PINECommand::MsgRead64 { .. } => PINEResponse::ResRead64 {
88                    val: read_u64(reader)?,
89                },
90                PINECommand::MsgWrite8 { .. } => PINEResponse::ResWrite8,
91                PINECommand::MsgWrite16 { .. } => PINEResponse::ResWrite16,
92                PINECommand::MsgWrite32 { .. } => PINEResponse::ResWrite32,
93                PINECommand::MsgWrite64 { .. } => PINEResponse::ResWrite64,
94                PINECommand::MsgVersion => PINEResponse::ResVersion {
95                    version: read_string(reader)?,
96                },
97                PINECommand::MsgSaveState { .. } => PINEResponse::ResSaveState,
98                PINECommand::MsgLoadState { .. } => PINEResponse::ResLoadState,
99                PINECommand::MsgTitle => PINEResponse::ResTitle {
100                    title: read_string(reader)?,
101                },
102                PINECommand::MsgID => PINEResponse::ResID {
103                    id: read_string(reader)?,
104                },
105                PINECommand::MsgUUID => PINEResponse::ResUUID {
106                    uuid: read_string(reader)?,
107                },
108                PINECommand::MsgGameVersion => PINEResponse::ResGameVersion {
109                    version: read_string(reader)?,
110                },
111                PINECommand::MsgStatus => PINEResponse::ResStatus {
112                    status: PINEStatus::from(read_u32(reader)?),
113                },
114                PINECommand::MsgUnimplemented => PINEResponse::ResUnimplemented,
115            });
116        }
117
118        Ok(res)
119    }
120}
121
122#[cfg(target_family = "unix")]
123impl PINE<UnixStream> {
124    pub fn connect_unix(target: &str, slot: u16, auto: bool) -> PINEResult<Self> {
125        let env_var = match env::consts::OS {
126            "linux" => "XDG_RUNTIME_DIR",
127            "macos" => "TMPDIR",
128            _ => return Err(PINEError::UnsupportedOS),
129        };
130        let dir = env::var(env_var).unwrap_or(String::from("/tmp"));
131        let filename = if auto {
132            format!("{target}.sock")
133        } else {
134            format!("{target}.sock.{slot}")
135        };
136        let path = Path::new(&dir).join(filename);
137        if !path.exists() {
138            return Err(PINEError::UnixSocket(path));
139        }
140
141        let stream = UnixStream::connect(path)?;
142        Ok(Self::from_stream(stream))
143    }
144
145    pub fn connect(target: &str, slot: u16, auto: bool) -> PINEResult<Self> {
146        Self::connect_unix(target, slot, auto)
147    }
148}
149
150impl PINE<TcpStream> {
151    pub fn connect_tcp(addr: Ipv4Addr, slot: u16) -> PINEResult<Self> {
152        let socket_addr = SocketAddrV4::new(addr, slot);
153        let stream = TcpStream::connect(socket_addr)?;
154        let mutex = Mutex::new(());
155        Ok(Self { stream, mutex })
156    }
157
158    #[cfg(target_family = "windows")]
159    pub fn connect(_target: &str, slot: u16, _auto: bool) -> PINEResult<Self> {
160        let addr = Ipv4Addr::new(127, 0, 0, 1);
161        Self::connect_tcp(addr, slot)
162    }
163}
164
165pub struct PINEBatch {
166    buffer: Vec<u8>,
167    commands: Vec<PINECommand>,
168}
169
170impl PINEBatch {
171    pub fn new() -> Self {
172        Self {
173            buffer: vec![0x00, 0x00, 0x00, 0x00],
174            commands: vec![],
175        } // First 4 bytes are for the message length
176    }
177
178    pub fn clear(&mut self) {
179        self.buffer.clear();
180        self.commands.clear();
181    }
182
183    pub fn add(&mut self, command: PINECommand) {
184        self.buffer.push(command.to_opcode());
185
186        match command {
187            PINECommand::MsgRead8 { mem } => self.buffer.extend_from_slice(&u32::to_le_bytes(mem)),
188            PINECommand::MsgRead16 { mem } => self.buffer.extend_from_slice(&u32::to_le_bytes(mem)),
189            PINECommand::MsgRead32 { mem } => self.buffer.extend_from_slice(&u32::to_le_bytes(mem)),
190            PINECommand::MsgRead64 { mem } => self.buffer.extend_from_slice(&u32::to_le_bytes(mem)),
191            PINECommand::MsgWrite8 { mem, val } => {
192                self.buffer.extend_from_slice(&u32::to_le_bytes(mem));
193                self.buffer.push(val);
194            }
195            PINECommand::MsgWrite16 { mem, val } => {
196                self.buffer.extend_from_slice(&u32::to_le_bytes(mem));
197                self.buffer.extend_from_slice(&u16::to_le_bytes(val));
198            }
199            PINECommand::MsgWrite32 { mem, val } => {
200                self.buffer.extend_from_slice(&u32::to_le_bytes(mem));
201                self.buffer.extend_from_slice(&u32::to_le_bytes(val));
202            }
203            PINECommand::MsgWrite64 { mem, val } => {
204                self.buffer.extend_from_slice(&u32::to_le_bytes(mem));
205                self.buffer.extend_from_slice(&u64::to_le_bytes(val));
206            }
207            PINECommand::MsgSaveState { sta } => self.buffer.push(sta),
208            PINECommand::MsgLoadState { sta } => self.buffer.push(sta),
209            _ => {}
210        }
211
212        self.commands.push(command);
213    }
214
215    fn finalize(&mut self) -> &[u8] {
216        let size = self.buffer.len() as u32;
217        self.buffer.splice(0..4, u32::to_le_bytes(size));
218        self.buffer.as_slice()
219    }
220}
221
222impl FromIterator<PINECommand> for PINEBatch {
223    fn from_iter<T: IntoIterator<Item = PINECommand>>(iter: T) -> Self {
224        let mut batch = PINEBatch::new();
225        for cmd in iter {
226            batch.add(cmd);
227        }
228        return batch;
229    }
230}
231
232impl Default for PINEBatch {
233    fn default() -> Self {
234        PINEBatch::new()
235    }
236}
237
238#[repr(u8)]
239#[derive(Clone, Copy, Debug)]
240pub enum PINECommand {
241    MsgRead8 { mem: u32 } = 0,
242    MsgRead16 { mem: u32 } = 1,
243    MsgRead32 { mem: u32 } = 2,
244    MsgRead64 { mem: u32 } = 3,
245    MsgWrite8 { mem: u32, val: u8 } = 4,
246    MsgWrite16 { mem: u32, val: u16 } = 5,
247    MsgWrite32 { mem: u32, val: u32 } = 6,
248    MsgWrite64 { mem: u32, val: u64 } = 7,
249    MsgVersion = 8,
250    MsgSaveState { sta: u8 } = 9,
251    MsgLoadState { sta: u8 } = 10,
252    MsgTitle = 11,
253    MsgID = 12,
254    MsgUUID = 13,
255    MsgGameVersion = 14,
256    MsgStatus = 15,
257    MsgUnimplemented = 255,
258}
259
260impl PINECommand {
261    fn to_opcode(&self) -> u8 {
262        match self {
263            PINECommand::MsgRead8 { .. } => 0,
264            PINECommand::MsgRead16 { .. } => 1,
265            PINECommand::MsgRead32 { .. } => 2,
266            PINECommand::MsgRead64 { .. } => 3,
267            PINECommand::MsgWrite8 { .. } => 4,
268            PINECommand::MsgWrite16 { .. } => 5,
269            PINECommand::MsgWrite32 { .. } => 6,
270            PINECommand::MsgWrite64 { .. } => 7,
271            PINECommand::MsgVersion => 8,
272            PINECommand::MsgSaveState { .. } => 9,
273            PINECommand::MsgLoadState { .. } => 10,
274            PINECommand::MsgTitle => 11,
275            PINECommand::MsgID => 12,
276            PINECommand::MsgUUID => 13,
277            PINECommand::MsgGameVersion => 14,
278            PINECommand::MsgStatus => 15,
279            PINECommand::MsgUnimplemented => 255,
280        }
281    }
282}
283
284impl Into<u8> for PINECommand {
285    fn into(self) -> u8 {
286        self.to_opcode()
287    }
288}
289
290impl std::fmt::Display for PINECommand {
291    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
292        write!(f, "{:?}", self)
293    }
294}
295
296#[repr(u8)]
297#[derive(Clone, Debug)]
298pub enum PINEResponse {
299    ResRead8 { val: u8 },
300    ResRead16 { val: u16 },
301    ResRead32 { val: u32 },
302    ResRead64 { val: u64 },
303    ResWrite8,
304    ResWrite16,
305    ResWrite32,
306    ResWrite64,
307    ResVersion { version: String },
308    ResSaveState,
309    ResLoadState,
310    ResTitle { title: String },
311    ResID { id: String },
312    ResUUID { uuid: String },
313    ResGameVersion { version: String },
314    ResStatus { status: PINEStatus },
315    ResUnimplemented,
316}
317
318impl std::fmt::Display for PINEResponse {
319    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
320        write!(f, "{:?}", self)
321    }
322}
323
324#[repr(u32)]
325#[derive(Clone, Debug)]
326pub enum PINEStatus {
327    Running = 0,
328    Paused = 1,
329    Shutdown = 2,
330    Unknown,
331}
332
333impl From<u32> for PINEStatus {
334    fn from(value: u32) -> Self {
335        match value {
336            0 => PINEStatus::Running,
337            1 => PINEStatus::Paused,
338            2 => PINEStatus::Shutdown,
339            _ => PINEStatus::Unknown,
340        }
341    }
342}
343
344impl std::fmt::Display for PINEStatus {
345    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
346        write!(f, "{:?}", self)
347    }
348}
349
350macro_rules! read_impl {
351    ($reader:ident, $ty:ident, $size:expr) => {
352        let mut buf: [u8; $size] = [0; $size];
353        $reader.read_exact(&mut buf)?;
354        return Ok($ty::from_le_bytes(buf));
355    };
356}
357
358fn read_u64<R: Read>(reader: &mut R) -> Result<u64, std::io::Error> {
359    read_impl!(reader, u64, 8);
360}
361fn read_u32<R: Read>(reader: &mut R) -> Result<u32, std::io::Error> {
362    read_impl!(reader, u32, 4);
363}
364fn read_u16<R: Read>(reader: &mut R) -> Result<u16, std::io::Error> {
365    read_impl!(reader, u16, 2);
366}
367fn read_u8<R: Read>(reader: &mut R) -> Result<u8, std::io::Error> {
368    read_impl!(reader, u8, 1);
369}
370fn read_string<R: Read>(reader: &mut R) -> Result<String, std::io::Error> {
371    let size = read_u32(reader)?;
372    let mut buffer: Vec<u8> = vec![0; size as usize];
373    reader.read_exact(buffer.as_mut_slice())?;
374    let mut s = std::str::from_utf8(&buffer).unwrap().to_string();
375    s.pop(); // Remove null terminator
376    Ok(s)
377}