use std::io::Read; use std::io::Result; use std::io::Seek; use std::io::SeekFrom; use std::io::Write; pub mod file; pub mod memory; pub mod shared; pub enum MapFlag { Read, Write, } trait VFileExtensions: VFile { fn readline(&mut self, buffer: &mut [u8]) -> Result { let mut byte = 0; while byte < buffer.len() - 1 { if self.read(&mut buffer[byte..byte + 1])? == 0 { break; } byte += 1; if buffer[byte - 1] == b'\n' { break; } } buffer[byte] = b'\0'; Ok(byte) } fn map(&mut self, size: usize, _flag: MapFlag) -> Result> { let position = self.stream_position()?; let data = vec![0; size]; let mut data = data.into_boxed_slice(); self.seek(SeekFrom::Start(0))?; match self.read_exact(&mut data) { Ok(_) => {} Err(err) => match err.kind() { std::io::ErrorKind::UnexpectedEof => {} _ => return Err(err), }, } self.seek(SeekFrom::Start(position))?; Ok(data) } fn unmap(&mut self, data: Box<[u8]>) -> Result<()> { // assume map was created with write let position = self.stream_position()?; self.seek(SeekFrom::Start(0))?; self.write_all(&data)?; self.seek(SeekFrom::Start(position))?; Ok(()) } fn truncate(&mut self, size: usize) -> Result<()> { let position = self.stream_position()?; let stream_length = self.seek(SeekFrom::End(0))?; if (size as u64) > stream_length { self.seek(SeekFrom::Start(position))?; return Ok(()); } self.seek(SeekFrom::Start(size as u64))?; let bytes_to_write = stream_length - size as u64; let bytes: Vec = std::iter::repeat(0).take(bytes_to_write as usize).collect(); self.write_all(&bytes)?; self.seek(SeekFrom::Start(position))?; Ok(()) } fn size(&mut self) -> Result { let position = self.stream_position()?; let stream_length = self.seek(SeekFrom::End(0))?; self.seek(SeekFrom::Start(position))?; Ok(stream_length as usize) } fn sync(&mut self, buffer: &[u8]) -> Result<()> { let position = self.stream_position()?; self.seek(SeekFrom::Start(0))?; self.write_all(buffer)?; self.seek(SeekFrom::Start(position))?; Ok(()) } } pub trait VFile: Seek + Read + Write {} impl VFileExtensions for T {} #[repr(C)] struct VFileInner { vfile: mgba_sys::VFile, file: V, } pub struct VFileAlloc(Box>); impl VFileAlloc { pub fn new(f: V) -> Self { Self(Box::new(VFileInner { vfile: unsafe { vfile_extern::create_vfile::() }, file: f, })) } pub(crate) fn into_mgba(self) -> *mut mgba_sys::VFile { let f = Box::into_raw(self.0) as *mut VFileInner; f.cast() } } mod vfile_extern { use std::io::SeekFrom; use super::VFileExtensions; /// Safety: Must be part of a VFileInner pub unsafe fn create_vfile() -> mgba_sys::VFile { mgba_sys::VFile { close: Some(close::), seek: Some(seek::), read: Some(read::), readline: Some(readline::), write: Some(write::), map: Some(map::), unmap: Some(unmap::), truncate: Some(truncate::), size: Some(size::), sync: Some(sync::), } } use mgba_sys::VFile; extern "C" fn close(vf: *mut VFile) -> bool { drop(unsafe { Box::from_raw(vf.cast::>()) }); true } unsafe fn with_inner(vf: *mut VFile, f: F) -> T where F: FnOnce(&mut dyn super::VFile) -> T, { let vf = vf.cast::>(); let vf = &mut *vf; f(&mut vf.file) } extern "C" fn seek( vf: *mut VFile, offset: mgba_sys::off_t, whence: std::os::raw::c_int, ) -> mgba_sys::off_t { unsafe { with_inner::(vf, |vf| { // casts required for windows compatability #[allow(clippy::useless_conversion)] let seek = match whence { libc::SEEK_CUR => SeekFrom::Current(offset.into()), libc::SEEK_SET => SeekFrom::Start(offset as u64), libc::SEEK_END => SeekFrom::End(offset.into()), _ => return -1, }; vf.seek(seek).map(|x| x as mgba_sys::off_t).unwrap_or(-1) }) } } extern "C" fn read( vf: *mut VFile, buffer: *mut ::std::os::raw::c_void, size: usize, ) -> isize { unsafe { with_inner::(vf, |vf| { vf.read(std::slice::from_raw_parts_mut(buffer.cast(), size)) .map(|x| x as isize) .unwrap_or(-1) }) } } extern "C" fn readline( vf: *mut VFile, buffer: *mut ::std::os::raw::c_char, size: usize, ) -> isize { unsafe { with_inner::(vf, |vf| { vf.readline(std::slice::from_raw_parts_mut(buffer.cast(), size)) .map(|x| x as isize) .unwrap_or(-1) }) } } extern "C" fn write( vf: *mut VFile, buffer: *const ::std::os::raw::c_void, size: usize, ) -> isize { unsafe { with_inner::(vf, |vf| { vf.write(std::slice::from_raw_parts(buffer.cast(), size)) .map(|x| x as isize) .unwrap_or(-1) }) } } extern "C" fn map( vf: *mut VFile, size: usize, flags: ::std::os::raw::c_int, ) -> *mut ::std::os::raw::c_void { unsafe { with_inner::(vf, |vf| { let map_type = match flags as u32 { mgba_sys::MAP_WRITE => super::MapFlag::Write, _ => super::MapFlag::Read, }; vf.map(size, map_type) .map(|x| Box::leak(x).as_mut_ptr().cast()) .unwrap_or(std::ptr::null_mut()) }) } } extern "C" fn unmap( vf: *mut VFile, memory: *mut ::std::os::raw::c_void, size: usize, ) { unsafe { with_inner::(vf, |vf| { let b = Box::from_raw(std::slice::from_raw_parts_mut(memory.cast::(), size)); let _ = vf.unmap(b); }) } } extern "C" fn truncate(vf: *mut VFile, size: usize) { unsafe { let _ = with_inner::(vf, |vf| vf.truncate(size)); } } extern "C" fn size(vf: *mut VFile) -> isize { unsafe { with_inner::(vf, |vf| vf.size().map(|x| x as isize).unwrap_or(-1)) } } extern "C" fn sync( vf: *mut VFile, buffer: *mut ::std::os::raw::c_void, size: usize, ) -> bool { unsafe { with_inner::(vf, |vf| { vf.sync(std::slice::from_raw_parts(buffer.cast(), size)) .is_ok() }) } } }