pine_ipc/
lib.rs

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