1use physfs_sys;
5use std::fmt;
6use std::sync::Mutex;
7
8use lazy_static::lazy_static;
9
10lazy_static! {
11 static ref INSTANCED: Mutex<bool> = Mutex::new(false);
12}
13
14pub struct PhysFsError {
16 code: physfs_sys::PHYSFS_ErrorCode,
17}
18
19impl PhysFsError {
20 pub(crate) fn new(code: physfs_sys::PHYSFS_ErrorCode) -> Self {
21 Self { code }
22 }
23
24 pub fn get_text(&self) -> String {
25 let v = unsafe {
26 std::ffi::CStr::from_ptr(physfs_sys::PHYSFS_getErrorByCode(self.code))
27 .to_bytes()
28 .to_vec()
29 };
30
31 String::from_utf8(v).unwrap()
32 }
33}
34
35impl fmt::Debug for PhysFsError {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 f.debug_struct("PhysFsError")
38 .field("code", &self.code)
39 .field("text", &self.get_text())
40 .finish()
41 }
42}
43
44impl fmt::Display for PhysFsError {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 write!(f, "{}", self.get_text())
47 }
48}
49
50use std::error::Error;
51
52impl Error for PhysFsError {
53 fn source(&self) -> Option<&(dyn Error + 'static)> {
54 None
55 }
56}
57
58pub struct PhysFsHandle {
60 handle: *mut physfs_sys::PHYSFS_File,
61}
62
63impl PhysFsHandle {
64 pub(crate) fn new(handle: *mut physfs_sys::PHYSFS_File) -> Self {
65 Self { handle }
66 }
67
68 pub fn file_length(&self) -> u64 {
69 unsafe { physfs_sys::PHYSFS_fileLength(self.handle) as u64 }
70 }
71
72 pub fn read_to_vec(&mut self) -> std::io::Result<Vec<u8>> {
73 let len = self.file_length() as usize;
74 let mut v = Vec::with_capacity(len);
75 v.resize(len, 0);
76 std::io::Read::read(self, v.as_mut_slice())?;
77 Ok(v)
78 }
79
80 pub fn close(self) {}
81}
82
83unsafe impl Send for PhysFsHandle {}
86unsafe impl Sync for PhysFsHandle {}
87
88impl std::io::Read for PhysFsHandle {
89 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
90 let read_bytes = unsafe {
91 physfs_sys::PHYSFS_readBytes(
92 self.handle,
93 buf.as_mut_ptr() as *mut std::ffi::c_void,
94 buf.len() as u64,
95 )
96 };
97 if read_bytes == -1 {
98 Err(std::io::Error::new(
99 std::io::ErrorKind::Other,
100 get_last_error().err().unwrap(),
101 ))
102 } else {
103 Ok(read_bytes as usize)
104 }
105 }
106}
107
108impl std::io::Write for PhysFsHandle {
109 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
110 Ok(unsafe {
111 physfs_sys::PHYSFS_writeBytes(
112 self.handle,
113 buf.as_ptr() as *const std::ffi::c_void,
114 buf.len() as u64,
115 ) as usize
116 })
117 }
118
119 fn flush(&mut self) -> std::io::Result<()> {
120 unsafe {
121 let ret = physfs_sys::PHYSFS_flush(self.handle);
122 if ret != 0 {
123 return Err(std::io::Error::new(
124 std::io::ErrorKind::Other,
125 get_last_error().err().unwrap(),
126 ));
127 }
128 }
129 Ok(())
130 }
131}
132
133use std::io::SeekFrom;
134impl std::io::Seek for PhysFsHandle {
135 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
136 match pos {
137 SeekFrom::Start(pos) => unsafe {
138 let ret = physfs_sys::PHYSFS_seek(self.handle, pos);
139 if ret == 0 {
140 Err(std::io::Error::new(
141 std::io::ErrorKind::Other,
142 get_last_error().err().unwrap(),
143 ))
144 } else {
145 Ok(physfs_sys::PHYSFS_tell(self.handle) as u64)
146 }
147 },
148 SeekFrom::Current(pos) => unsafe {
149 let cpos = physfs_sys::PHYSFS_tell(self.handle);
150 self.seek(SeekFrom::Start((cpos + pos) as u64))
151 },
152 SeekFrom::End(pos) => unsafe {
153 let cpos = physfs_sys::PHYSFS_fileLength(self.handle);
154 self.seek(SeekFrom::Start((cpos + pos) as u64))
155 },
156 }
157 }
158}
159
160impl Drop for PhysFsHandle {
161 fn drop(&mut self) {
162 unsafe {
163 let _res = physfs_sys::PHYSFS_close(self.handle);
164 }
166 }
167}
168
169fn get_last_error() -> Result<(), PhysFsError> {
170 Err(unsafe { PhysFsError::new(physfs_sys::PHYSFS_getLastErrorCode()) })
171}
172
173pub struct PhysFs {}
178
179fn create_ctsr(s: impl AsRef<str>) -> std::ffi::CString {
180 std::ffi::CString::new(s.as_ref().as_bytes()).unwrap()
181}
182
183macro_rules! to_cstr {
184 ($s:expr) => {
185 create_ctsr($s).as_bytes().as_ptr() as *const i8
186 };
187}
188
189impl PhysFs {
190 fn new() -> Self {
191 unsafe {
192 physfs_sys::PHYSFS_init(std::ptr::null());
193 }
194
195 Self {}
196 }
197
198 pub fn get() -> Option<Self> {
201 let mut instanced_lock = INSTANCED.lock().unwrap();
202 let ret = if !*instanced_lock { Some(Self::new()) } else { None };
203 *instanced_lock = true;
204
205 ret
206 }
207
208 pub fn mount(
211 &mut self,
212 dir: impl AsRef<str>,
213 mount_point: impl AsRef<str>,
214 append: bool,
215 ) -> Result<(), PhysFsError> {
216 unsafe {
217 if 0 == physfs_sys::PHYSFS_mount(
218 to_cstr!(dir),
219 to_cstr!(mount_point),
220 if append { 1 } else { 0 },
221 ) {
222 get_last_error()?;
223 }
224 }
225
226 Ok(())
227 }
228
229 unsafe fn make_handle(
230 &self,
231 handle: *mut physfs_sys::PHYSFS_File,
232 ) -> Result<PhysFsHandle, PhysFsError> {
233 if handle == std::ptr::null_mut() {
234 get_last_error()?;
235 }
236
237 Ok(PhysFsHandle::new(handle))
238 }
239
240 pub fn open_read(&self, path: impl AsRef<str>) -> Result<PhysFsHandle, PhysFsError> {
242 unsafe { self.make_handle(physfs_sys::PHYSFS_openRead(to_cstr!(path))) }
243 }
244
245 pub fn open_write(&self, path: impl AsRef<str>) -> Result<PhysFsHandle, PhysFsError> {
247 unsafe { self.make_handle(physfs_sys::PHYSFS_openWrite(to_cstr!(path))) }
248 }
249
250 pub fn open_append(&self, path: impl AsRef<str>) -> Result<PhysFsHandle, PhysFsError> {
253 unsafe { self.make_handle(physfs_sys::PHYSFS_openAppend(to_cstr!(path))) }
254 }
255
256 pub fn enumerate_files(&self, path: impl AsRef<str>) -> Option<Vec<String>> {
258 let mut list = unsafe {physfs_sys::PHYSFS_enumerateFiles(to_cstr!(path))};
259 if list == std::ptr::null_mut() {
260 return None;
261 }
262
263 let mut res = vec![];
264 while unsafe{*list} != std::ptr::null_mut() {
265 unsafe {
266 let filename = std::ffi::CStr::from_ptr(*list).to_str().unwrap();
267 res.push(filename.to_owned());
268 }
269 list = ((list as usize) + std::mem::size_of_val(&list)) as _;
270 }
271
272 return Some(res);
273 }
274
275 pub fn is_directory(&self, path: impl AsRef<str>) -> bool {
276 unsafe{
277 physfs_sys::PHYSFS_isDirectory(to_cstr!(path)) == 1
278 }
279 }
280}
281
282impl Drop for PhysFs {
283 fn drop(&mut self) {
284 let mut instanced_lock = INSTANCED.lock().unwrap();
285 *instanced_lock = false;
286 }
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292 use std::io::*;
293
294 fn wait_instance() -> PhysFs {
296 let mut instance = None;
297 while instance.is_none() {
298 instance = PhysFs::get();
299 }
300 instance.unwrap()
301 }
302
303 const TEST_TEXT: &'static str = "testing\n";
304 #[test]
305 fn it_works() {
306 let mut fs = wait_instance();
307 fs.mount("alol.zip", "", true).err().unwrap(); fs.mount("test.zip", "", true).unwrap();
309 fs.mount("./", "", true).unwrap();
310 let mut handle = fs.open_read("test").unwrap();
311
312 assert_eq!(handle.file_length(), 8);
313 let mut buf = [0; 8];
314 handle.read(&mut buf).unwrap();
315 assert_eq!(String::from_utf8_lossy(&buf), TEST_TEXT);
316 handle.close();
317
318 let mut handle = fs.open_read("test").unwrap();
319 assert_eq!(handle.read_to_vec().unwrap(), TEST_TEXT.as_bytes().to_vec());
320
321 assert!(PhysFs::get().is_none());
322 }
323
324 #[test]
325 fn files_enumeration() {
326 let mut fs = wait_instance();
327 fs.mount("test.zip", "", true).unwrap();
328
329 let files = fs.enumerate_files("/").unwrap();
330 assert_eq!(vec!["test"], files);
331 assert!(!fs.is_directory("test"));
332 fs.mount("./", "/", true);
333 assert!(fs.is_directory("src"));
334 }
335}