#![allow(missing_docs)]
use crate::std::AsCStr;
use crate::sys::kernel;
use core::ffi::CStr;
use core::fmt;
use core::fmt::{Debug, Formatter};
use core::marker::PhantomData;
use core::mem::forget;
#[cfg(feature = "nightlier")]
use core::sync::atomic::AtomicBool;
#[cfg(feature = "nightlier")]
use core::sync::atomic::Ordering;
pub fn init_filesystem() {
unsafe {
kernel::psx_set_default_exit_from_exception();
kernel::psx_init_card(true);
kernel::psx_start_card();
}
}
pub fn close_filesystem() {
unsafe {
kernel::psx_stop_card();
}
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct DirEntry {
file_name: [u8; 0x14],
_attribute: u32,
file_size: u32,
_unused: u32,
_first_sector: u32,
_reserved: u32,
}
impl DirEntry {
pub unsafe fn from_bytes(ptr: *const [u8; 40]) -> DirEntry {
*ptr.cast()
}
}
impl Debug for DirEntry {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("DirEntry")
.field("file_name", &CStr::from_bytes_until_nul(&self.file_name))
.field("file_size", &self.file_size)
.finish()
}
}
pub unsafe fn first_file<P: AsRef<[u8]>>(path: P) -> Option<DirEntry> {
path.as_cstr(|path| {
let dir_entry = kernel::psx_first_file(path.as_ptr());
if dir_entry.is_null() {
None
} else {
Some(DirEntry::from_bytes(dir_entry))
}
})
}
pub unsafe fn next_file() -> Option<DirEntry> {
let dir_entry = kernel::psx_next_file();
if dir_entry.is_null() {
None
} else {
Some(DirEntry::from_bytes(dir_entry))
}
}
pub trait FileTy: Default {
const SECTOR_SIZE: usize;
}
#[derive(Default, Debug)]
pub struct MemCard;
impl FileTy for MemCard {
const SECTOR_SIZE: usize = 128;
}
#[derive(Default, Debug)]
pub struct CDROM;
impl FileTy for CDROM {
const SECTOR_SIZE: usize = 2048;
}
#[derive(Default)]
pub struct OpenOptions<T: FileTy> {
create: bool,
async_mode: bool,
blocks: u16,
_ty: PhantomData<T>,
}
impl<T: FileTy> OpenOptions<T> {
pub fn new() -> Self {
Self::new_impl()
}
#[cfg(not(feature = "nightlier"))]
fn new_impl() -> Self {
static mut FILESYSTEM_INITIALIZED: bool = false;
unsafe {
if !FILESYSTEM_INITIALIZED {
FILESYSTEM_INITIALIZED = true;
init_filesystem()
};
}
Default::default()
}
#[cfg(feature = "nightlier")]
fn new_impl() -> Self {
static FILESYSTEM_INITIALIZED: AtomicBool = AtomicBool::new(false);
if !FILESYSTEM_INITIALIZED.load(Ordering::Relaxed) {
FILESYSTEM_INITIALIZED.store(true, Ordering::Relaxed);
init_filesystem()
};
Default::default()
}
pub fn open<'f, P: AsRef<[u8]>>(&self, path: P) -> Result<File<T>, Error<'f, T>> {
path.as_cstr(|path| {
let fd = unsafe { kernel::psx_file_open(path.as_ptr(), self.flags()) };
match fd {
i8::MIN..=-2 => Err(Error::Resolved(ErrorKind::UnknownError)),
-1 => Err(Error::Unresolved),
0..=i8::MAX => Ok(File {
fd,
_ty: PhantomData,
}),
}
})
}
fn flags(&self) -> u32 {
1 | ((self.create as u32) << 9) |
((self.async_mode as u32) << 15) |
((self.blocks as u32) << 16)
}
}
impl OpenOptions<MemCard> {
pub fn async_mode(&mut self, mode: bool) -> &mut Self {
self.async_mode = mode;
self
}
pub fn create(&mut self, blocks: u16) -> &mut Self {
self.blocks = blocks;
self.create = true;
self
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ErrorKind {
NoError,
FileNotFound,
BadDevPort,
InvalidFileHandle,
PhysicalError,
FileAlreadyExists,
AttemptedToMoveDevices,
UnknownDevice,
SectorAlignmentError,
NoFreeHandles,
NoFreeBlocks,
UnknownError,
UnalignedBuffer,
}
impl From<u32> for ErrorKind {
fn from(err: u32) -> Self {
match err {
0x00 => ErrorKind::NoError,
0x02 => ErrorKind::FileNotFound,
0x06 => ErrorKind::BadDevPort,
0x09 | 0xFFFF_FFFF => ErrorKind::InvalidFileHandle,
0x10 => ErrorKind::PhysicalError,
0x11 => ErrorKind::FileAlreadyExists,
0x12 => ErrorKind::AttemptedToMoveDevices,
0x13 => ErrorKind::UnknownDevice,
0x16 => ErrorKind::SectorAlignmentError,
0x18 => ErrorKind::NoFreeHandles,
0x1C => ErrorKind::NoFreeBlocks,
_ => ErrorKind::UnknownError,
}
}
}
#[derive(Debug)]
pub enum Error<'f, T: FileTy> {
Resolved(ErrorKind),
Unresolved,
UnresolvedFile {
file: &'f File<T>,
#[doc(hidden)]
_ty: PhantomData<T>,
},
}
impl<'f, T: FileTy> Error<'f, T> {
pub fn kind(&self) -> ErrorKind {
match self {
Error::Resolved(kind) => *kind,
Error::Unresolved => {
let err = unsafe { kernel::psx_get_last_error() };
ErrorKind::from(err)
},
Error::UnresolvedFile { file, _ty } => {
let err = unsafe { kernel::psx_get_last_file_error(file.fd) };
ErrorKind::from(err)
},
}
}
}
pub enum SeekFrom {
Start(u32),
Current(i32),
}
#[derive(Debug)]
pub struct File<T: FileTy> {
fd: i8,
_ty: PhantomData<T>,
}
impl<T: FileTy> File<T> {
pub fn open(path: &str) -> Result<File<T>, Error<T>> {
OpenOptions::new().open(path)
}
pub fn seek(&self, pos: SeekFrom) -> Result<usize, Error<T>> {
let (offset, seek_ty) = match pos {
SeekFrom::Start(offset) => (offset, 0),
SeekFrom::Current(offset) => (offset as u32, 1),
};
let res = unsafe { kernel::psx_file_seek(self.fd, offset, seek_ty) };
self.try_return_usize(res)
}
pub fn read(&self, dst: &mut [u8]) -> Result<usize, Error<T>> {
if !dst.as_ptr().cast::<u32>().is_aligned() {
return Err(Error::Resolved(ErrorKind::UnalignedBuffer))
}
let res = unsafe { kernel::psx_file_read(self.fd, dst.as_mut_ptr().cast(), dst.len()) };
self.try_return_usize(res)
}
pub fn close<'f>(self) -> Result<i8, Error<'f, T>> {
let res = unsafe { kernel::psx_file_close(self.fd) };
forget(self);
match res {
i8::MIN..=-2 => Err(Error::Resolved(ErrorKind::UnknownError)),
-1 => Err(Error::Unresolved),
0..=i8::MAX => Ok(res),
}
}
fn try_return_usize(&self, res: i32) -> Result<usize, Error<T>> {
match res {
i32::MIN..=-2 => Err(Error::Resolved(ErrorKind::UnknownError)),
-1 => Err(Error::UnresolvedFile {
file: self,
_ty: PhantomData,
}),
0..=i32::MAX => Ok(res as usize),
}
}
}
impl File<MemCard> {
pub fn new(path: &str, size: usize) -> Result<File<MemCard>, Error<MemCard>> {
let blocks = size >> 13;
OpenOptions::new().create(blocks as u16).open(path)
}
pub fn write(&mut self, src: &[u8]) -> Result<usize, Error<MemCard>> {
if !src.as_ptr().cast::<u32>().is_aligned() {
return Err(Error::Resolved(ErrorKind::UnalignedBuffer))
}
let res = unsafe { kernel::psx_file_write(self.fd, src.as_ptr().cast(), src.len()) };
self.try_return_usize(res)
}
}
impl<T: FileTy> Drop for File<T> {
fn drop(&mut self) {
let _res = unsafe { kernel::psx_file_close(self.fd) };
}
}