use std::{
ffi::{CStr, CString},
fmt::Display,
io::Read,
path::Path,
str::FromStr,
};
use crate::{
bindings::filesystem::{FileHandle_t, VPKData},
errors::FsOpenError,
mid::filesystem::{FileSystemSys, FILE_SYSTEM_SYS},
};
pub fn get_fs() -> &'static FileSystemSys {
FILE_SYSTEM_SYS.wait()
}
pub struct VFile {
inner: FileHandle_t,
}
pub struct VFileReader<'a> {
file: &'a VFile,
fs: &'static FileSystemSys,
}
impl VFile {
pub const fn get_inner(&self) -> &FileHandle_t {
&self.inner
}
pub fn reader(&'_ self) -> VFileReader<'_> {
VFileReader {
file: self,
fs: FILE_SYSTEM_SYS.wait(),
}
}
}
impl Display for VFile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut buf = String::new();
_ = self.reader().read_to_string(&mut buf);
f.write_str(&buf)
}
}
impl Drop for VFile {
fn drop(&mut self) {
let fs = FILE_SYSTEM_SYS.wait();
(fs.vtable2().Close)(fs.get_raw(), self.inner);
}
}
impl<'a> Read for VFileReader<'a> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
Ok((self.fs.vtable2().Read)(
&self.fs.get_raw().vtable2,
buf.as_mut_ptr().cast(),
buf.len() as i32,
self.file.inner,
) as usize)
}
}
pub fn open_with_options<'a>(
path: &'a Path,
options: &CStr,
path_id: &CStr,
) -> Result<VFile, FsOpenError<'a>> {
let fs = *FILE_SYSTEM_SYS.wait();
let cstr_path = CString::from_str(path.to_str().ok_or(FsOpenError::PathToStringFailed(path))?)?;
if !(fs.vtable2().FileExists)(&fs.get_raw().vtable2, cstr_path.as_ptr(), path_id.as_ptr()) {
return Err(FsOpenError::NotFound(path));
}
let file = (fs.vtable2().Open)(
&fs.get_raw().vtable2,
cstr_path.as_ptr(),
options.as_ptr(),
path_id.as_ptr(),
0,
);
Ok(VFile { inner: file })
}
pub fn open<'a>(file: &'a Path) -> Result<VFile, FsOpenError<'a>> {
open_with_options(file, c"rb", c"GAME")
}
pub fn mount_vpk(vpk_path: &Path) -> Option<&'static VPKData> {
let fs = *FILE_SYSTEM_SYS.wait();
unsafe {
(fs.vtable().MountVPK)(
fs.get_raw(),
CString::from_str(vpk_path.to_str()?).ok()?.as_ptr(),
)
.as_ref()
}
}