psyk/
io.rs

1// Copyright (c)\x2025 joaoviictorti
2// Licensed under the MIT License. See LICENSE file in the project root for details.
3
4use std::fmt::{Debug, Display, Formatter};
5use std::fs::File;
6use std::io::Write;
7use std::path::Path;
8
9use crate::{display, LIB, OBJ};
10use anyhow::{anyhow, bail, Result};
11use binrw::io::Cursor;
12use binrw::{meta::ReadMagic, BinRead, BinWrite};
13
14#[derive(Debug)]
15pub enum Type {
16    OBJ(OBJ),
17    LIB(LIB),
18}
19
20impl Display for Type {
21    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
22        match self {
23            Self::OBJ(obj) => obj as &dyn Display,
24            Self::LIB(lib) => lib as &dyn Display,
25        }
26        .fmt(f)
27    }
28}
29
30impl display::DisplayWithOptions for Type {
31    fn fmt_with_options(&self, f: &mut Formatter, options: &display::Options) -> std::fmt::Result {
32        match self {
33            Self::OBJ(obj) => obj as &dyn display::DisplayWithOptions,
34            Self::LIB(lib) => lib as &dyn display::DisplayWithOptions,
35        }
36        .fmt_with_options(f, options)
37    }
38}
39
40pub fn read_bytes(path: &Path) -> Result<Vec<u8>> {
41    if !Path::exists(path) {
42        bail!(format!("File not found: {}", path.display()));
43    }
44
45    Ok(std::fs::read(path)?)
46}
47
48/// Reads a Psy-Q [LIB] or [OBJ]. If the file cannot be found or if the file
49/// does not contain valid data an error will be returned.
50pub fn read(lib_or_obj_path: &Path) -> Result<Type> {
51    let bytes = read_bytes(lib_or_obj_path)?;
52
53    if bytes.len() < 3 {
54        bail!("File too small to contain valid PSY-Q magic number");
55    }
56
57    let mut magic: [u8; 3] = [0; 3];
58    magic.clone_from_slice(&bytes[0..3]);
59    let mut data = Cursor::new(&bytes);
60
61    match magic {
62        LIB::MAGIC => Ok(Type::LIB(LIB::read(&mut data).map_err(|e| anyhow!(e))?)),
63        OBJ::MAGIC => Ok(Type::OBJ(OBJ::read(&mut data).map_err(|e| anyhow!(e))?)),
64        _ => bail!(format!("Unrecognized magic {:?}", &bytes[0..3])),
65    }
66}
67
68/// Reads a Psy-Q [OBJ]. If the file cannot be found or if the file
69/// does not contain valid data an error will be returned.
70pub fn read_obj(obj_path: &Path) -> Result<OBJ> {
71    let bytes = read_bytes(obj_path)?;
72    let mut data = Cursor::new(&bytes);
73    OBJ::read(&mut data).map_err(|e| anyhow!(e))
74}
75
76/// Reads a Psy-Q [LIB]. If the file cannot be found or if the file
77/// does not contain valid data an error will be returned.
78pub fn read_lib(lib_path: &Path) -> Result<LIB> {
79    let bytes = read_bytes(lib_path)?;
80    let mut data = Cursor::new(&bytes);
81    LIB::read(&mut data).map_err(|e| anyhow!(e))
82}
83
84/// Writes a Psy-Q [OBJ]. If the file cannot be written an error will
85/// be returned.
86pub fn write_obj(obj: &OBJ, file: &mut File) -> Result<()> {
87    let mut writer = Cursor::new(Vec::new());
88    obj.write(&mut writer).map_err(|e| anyhow!(e))?;
89    let gen = writer.into_inner();
90    file.write_all(&gen)?;
91    Ok(())
92}
93
94/// Writes a Psy-Q [LIB]. If the file cannot be written an error will
95/// be returned.
96pub fn write_lib(lib: &LIB, file: &mut File) -> Result<()> {
97    let mut writer = Cursor::new(Vec::new());
98    lib.write(&mut writer).map_err(|e| anyhow!(e))?;
99    let gen = writer.into_inner();
100    file.write_all(&gen)?;
101    Ok(())
102}