1 /*
2 Copyright (C) 2013 Michael D. Parker
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 
6 Permission is hereby granted, free of charge, to any person or organization
7 obtaining a copy of the software and accompanying documentation covered by
8 this license (the "Software") to use, reproduce, display, distribute,
9 execute, and transmit the Software, and to prepare derivative works of the
10 Software, and to permit third-parties to whom the Software is furnished to
11 do so, all subject to the following:
12 
13 The copyright notices in the Software and this entire statement, including
14 the above license grant, this restriction and the following disclaimer,
15 must be included in all copies of the Software, in whole or in part, and
16 all derivative works of the Software, unless such copies or derivative
17 works are solely in the form of machine-executable object code generated by
18 a source language processor.
19 
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 DEALINGS IN THE SOFTWARE.
27 
28 */
29 module defile.defile;
30 
31 private {
32     import std..string;
33     import std.conv;
34     import std.traits;
35 
36     import derelict.physfs.physfs;
37 }
38 
39 class DefileException : Exception
40 {
41     public this(string msg, string file = __FILE__, size_t line = __LINE__)
42     {
43         this(msg, true, file, line);
44     }
45 
46     public this(string msg, bool getErrString, string file = __FILE__, size_t line = __LINE__)
47     {
48         if(getErrString) {
49             msg = format("%s: %s", msg, Defile.lastError);
50         }
51         super(msg, file, line, null);
52     }
53 }
54 
55 enum OpenFor {
56     read,
57     write,
58     append,
59 }
60 
61 enum ConfigFlags {
62     none = 0x0,
63     includeCDRoms = 0x1,
64     archivesFirst = 0x2,
65 }
66 
67 enum PathType {
68     write,
69     base,
70 }
71 
72 enum MountAction {
73     prepend,
74     append,
75 }
76 
77 /++
78     A wrapper of the PhysicsFS library, specifically via the DerelictPHYSFS binding.
79 
80     In some cases, the methods of Defile directly wrap PhysicsFS functions, doing nothing
81     more than converting between C and D types and throwing exceptions when a call
82     fails. Other methods are for convenience, wrapping multiple PhysicsFS function
83     calls into a single method. For more information on the details of the wrapped
84     PhysicsFS functions, please refer either to the PHYSFS documentation or physfs.h.
85 
86     Note that the static methods below either wrap functions that work with global
87     state or serve as covenience methods that eliminate the need to deal with an
88     indivdual file. The nonstatic methods wrap functions that manipulate files
89     directly.
90 +/
91 struct Defile
92 {
93 public:
94     /++
95         Must be called before any other methods.
96 
97         Calling other methods without first calling initialize should be
98         considered as undefined behavior. The result will almost surely
99         be a crash, since this method loads and initializes the PhysicsFS library.
100 
101         Throws:
102             DerelictException if the physfs library fails to load.
103             DefileException if the physfs library fails to initialize.
104     +/
105     static void initialize()
106     {
107         import core.runtime;
108 
109         version(Win64)
110             enum physfsDLL = "physfs-x86_64.dll";
111         else version(Win32)
112             enum physfsDLL = "physfs-x86.dll";
113         else
114             enum physfsDLL = "";
115 
116         DerelictPHYSFS.load(physfsDLL);
117 
118         if(PHYSFS_init(Runtime.args[ 0 ].toStringz()) == 0) {
119             throw new DefileException("Failed to initialize virtual file system");
120         }
121 
122         _baseDir = to!string(PHYSFS_getBaseDir());
123     }
124 
125     /++
126         Should be called before the application exits.
127 
128         Calling other methods after calling terminate should be considered
129         as undefined behavior. The result could vary between crashes due to
130         access violations and the throwing of DefileExceptions. It is safe to
131         call this even if the initialize method causes an exception to be
132         thrown, so, e.g., wrapping it in scope(exit) is preferable to
133         scope(success).
134     +/
135     static void terminate()
136     {
137         if(DerelictPHYSFS.isLoaded) {
138             PHYSFS_deinit();
139         }
140     }
141 
142     /++
143         Creates the application write directory.
144 
145         This function only ensures the write directory is created. It does not
146         add it to the search path. Until this function is called, the
147         Defile.writeDir property is invalid.
148 
149         Params:
150             organization    = The name to be used as the top-level
151                               of the app's write directory tree. Should be the name of
152                               your group or company.
153             appName         = The name of the application. Will be a subdirectory
154                               under 'organization' if organization is null, or the
155                               app's top-level write directory.
156         Throws:
157             DefileException if the call fails.
158     +/
159     static void createWriteDir(string organization, string appName)
160     {
161         auto cstr = PHYSFS_getPrefDir(organization.toStringz(), appName.toStringz());
162         if(!cstr) throw new DefileException("Failed to create application write directory");
163 
164         _writeDir = to!string(cstr);
165     }
166 
167     /*
168         Creates the write directory and adds it to the search path, followed by the base
169         directory.
170 
171         It is common in games to search for files first in the write directory, then in
172         the base directory. This allows default files that ship with the game to be
173         maintained in the base directory, then overidden on loading by searching in
174         the write directory first. If this configuration does not suit your application,
175         then do not call this function. Instead, call createWriteDir directly and
176         manually configure the search path using the mount function.
177 
178         Params:
179             organization    = The name to be used as the top-level
180                               of the app's write directory tree. Should be the name of
181                               your group or company.
182             appName         = The name of the application. Will be a subdirectory
183                               under 'organization' if organization is null, or the
184                               app's top-level write directory.
185         Throws:
186             DefileException if the call fails.
187     */
188     static void createDefaultSearchPath(string organization, string appName)
189     {
190         createWriteDir(organization, appName);
191         mount(_writeDir);
192         mount(_baseDir);
193     }
194 
195     /++
196         A wrapper for PHYSFS_mkdir.
197 
198         Creates a directory on the local file system, including any parent
199         directories in the specified path that do not exist. See the documentation
200         for PHYSFS_mkdir for details.
201 
202         Params:
203             dirName = The relative path in the virtual file system of the
204                       directory to create.
205         Throws:
206             DefileException if the call fails.
207     +/
208     static void mkdir(string dirPath)
209     {
210         if(PHYSFS_mkdir(dirPath.toStringz()) == 0) {
211             throw new DefileException("Failed to create directory " ~ dirPath);
212         }
213     }
214 
215     /++
216         A wrapper for PHYSFS_delete.
217 
218         Deletes a file or directory from the physical file system. See the
219         documentation for PHYSFS_delete for details.
220 
221         Params:
222             path    = The relative path in the virtual file system to the
223                       file or directory to delete.
224         Throws:
225             DefileException if an error occurs.
226     +/
227     static void remove(string path)
228     {
229         if(PHYSFS_delete(path.toStringz()) == 0) {
230             throw new DefileException("Failed to delete file/directory " ~ path);
231         }
232     }
233 
234     /++
235         A wrapper for PHYSFS_exists.
236 
237         Params:
238             filePath = a fileName or relative file path for which to search
239                        in the virtual file system search path.
240         Returns:
241             true if the given path exists anywhere in the PhysicsFS search
242             path and false if it does not.
243     +/
244     static bool exists(string filePath)
245     {
246         return PHYSFS_exists(filePath.toStringz()) != 0;
247     }
248 
249     /++
250         A weapper for PHYSFS_mount.
251 
252         Adds a directory or archive to the virtual file system search path.
253         See the documentation for PHYSFS_mount for details.
254 
255         Params:
256             newDir      = directory or archive to add to the search path.
257             mountPoint  = location in the tree in which to add newDir. null
258                           or "" is equivalent to "/". This is the default.
259             action      = indicates if the directory should be appended or prepended
260                           to the search path. The default is MountAction.append.
261         Throws:
262             DefileException if the call fails.
263     +/
264     static void mount(string newDir, string mountPoint = "", MountAction action = MountAction.append)
265     {
266         auto mp = mountPoint is null ? null : mountPoint.toStringz();
267         if(PHYSFS_mount(newDir.toStringz(), mp, action) == 0) {
268             throw new DefileException("Failed to mount " ~ newDir);
269         }
270     }
271 
272     /++
273         A covenience function that reads the entire content of a file in a
274         single method call.
275 
276         The method will first open for reading the file specified by filePath
277         and determine its length. Then it will call the Defile.read method of
278         the file instance, which will allocate or expand the provided buffer
279         as necessary.
280 
281         Params:
282             filePath    = The relative path to the file in the virtual file system.
283             buffer      = The buffer in which the content of the file will be
284                           stored. The buffer will be allocated if null and
285                           expanded if too small.
286         Returns:
287             The number of bytes read.
288         Throws:
289             DefileException if an error occurs.
290     +/
291     static size_t readFile(string filePath, ref ubyte[] buffer)
292     {
293         auto file = Defile(filePath, OpenFor.read);
294         auto size = file.length;
295         auto ret = file.read(buffer, size, 1);
296         return ret * size;
297     }
298 
299     /++
300         A convenience function that writes an entire buffer to a file in
301         a single method call.
302 
303         The method will first open for writing the file specified by filePath,
304         the will call Defile.write to completely write buffer to the file.
305 
306         Params:
307             filePath    = The relative path to the file in the virtual file system.
308             buffer      = The bytes that will be written to the file.
309         Returns:
310             The number of bytes written, which should equal buffer.length.
311         Throws:
312             DefileException if an error occurs.
313     +/
314     static size_t writeFile(string filePath, ubyte[] buffer)
315     {
316         auto file = Defile(filePath, OpenFor.write);
317         auto ret = file.write(buffer, buffer.length, 1);
318         return ret * buffer.length;
319     }
320 
321     /++
322         A convenience function which creates a path string to a file in a
323         specific directory.
324 
325         Sometimes, a file in the write, base or user directories may need to
326         be opened outside of the virtual file system. In those cases, it is
327         necessary to query Defile for the path to the directory of interest
328         and construct the fill file path. This method condenses that into
329         one call.
330 
331         Note that this function does not determine if the file exists. It only
332         builds the path.
333 
334         Params:
335             which       = Specifies which directory will comprise the path. Either
336                           PathType.Write or PathType.Base.
337             fileName    = The name of the file that will be appended to the path.
338         Returns:
339             A relative path in the virtual file system.
340     +/
341     static string makeFilePath(PathType which, string fileName)
342     {
343         version(Windows) string fmtString = "%s\\%s";
344         else string fmtString = "%s/%s";
345 
346         with(PathType) final switch(which) {
347             case write:
348                 assert(_writeDir !is null, "Set write dir before using it in makeFilePath");
349                 return format(fmtString, _writeDir, fileName);
350 
351             case base:
352                 return format(fmtString, _baseDir, fileName);
353         }
354     }
355 
356     /++
357         Searches for a given file in the write and base directories and, if
358         it exists, returns a path to the file.
359 
360         This method first looks for the file in the write directory. If it
361         exists, then a string containing the path "writeDir/fileName" is
362         returned. Otherwise, it then looks for the file in the base directory
363         and returns its path if found. If the file exists in neither directory,
364         the method returns null.
365 
366         Params:
367             fileName    = The name or relative path of a file to look for.
368         Returns:
369             A string containing the relative path to the file in the virtual
370             file system, or null if the file cannot be found.
371     +/
372     static string findFilePath(string fileName)
373     {
374         auto path = makeFilePath(PathType.write, fileName);
375         if(exists(path)) return path;
376 
377         path = makeFilePath(PathType.base, fileName);
378         if(exists(path)) return path;
379 
380         return null;
381     }
382 
383     /++
384         A wrapper for PHYSFS_getLastError().
385 
386         Returns:
387             An string describing the last error to occur in a PHYSFS
388             function call.
389     +/
390     static string lastError()
391     {
392         return to!string(PHYSFS_getLastError());
393     }
394 
395     /++
396         Returns:
397             The application's base directory, i.e. where the executable lives.
398     +/
399     static string baseDir()
400     {
401         return _baseDir;
402     }
403 
404     /++
405         Returns:
406             The current write directory.
407     +/
408     static string writeDir()
409     {
410         assert(_writeDir !is null);
411         return _writeDir;
412     }
413 
414     /++
415         Sets the current write directory.
416 
417         All calls to writeFile or Defile.write will be directed to the
418         directory specified here.
419 
420         Params:
421             dir = The new write directory.
422         Throws:
423             DefileException if the call fails.
424     +/
425     static void writeDir(string dir)
426     {
427         auto ret = PHYSFS_setWriteDir(dir.toStringz());
428         if(ret == 0) {
429             throw new DefileException("Failed to set write directory " ~ dir);
430         }
431         _writeDir = dir;
432     }
433 
434     /++
435         Returns:
436             An array of strings containing each individual path that is
437             on the virtual file system search path.
438     +/
439     static string[] searchPath()
440     {
441         string[] ret;
442         auto list = PHYSFS_getSearchPath();
443         for(size_t i = 0; list[ i ]; ++i) {
444             ret ~= to!string(list[ i ]);
445         }
446         PHYSFS_freeList(list);
447         return ret;
448     }
449 
450     /++
451         Opens a file when it is constructed.
452     +/
453     this(string fileName, OpenFor ofor)
454     {
455         open(fileName, ofor);
456     }
457 
458     /++
459         Closes a file when it goes out of scope.
460     +/
461     ~this()
462     {
463         close();
464     }
465 
466     /++
467         A wrapper for PHYSFS_openRead, PHYSFS_openWrite, and PHYSFS_openAppend.
468 
469         A file must be opened before any operations can be performed on it.
470         Failure to do so should be considered undefined behavior, but will
471         most likely result in exceptions being thrown.
472 
473         See the documentation for PHYSFS_openRead, PHYSFS_openWrite and
474         PHYSFS_openAppend for more details.
475 
476         Params:
477             fileName    = The relative path to the file in the virtual file system.
478             ofor        = The usage for which the file will be opened, one of
479                           OpenFor.Read, OpenFor.Write, or OpenFor.Append.
480         Throws:
481             DefileException if the file could not be opened.
482     +/
483     void open(string fileName, OpenFor ofor)
484     {
485         auto cname = fileName.toStringz();
486         with(OpenFor) final switch(ofor) {
487             case read:
488                 _handle = PHYSFS_openRead(cname);
489                 break;
490 
491             case write:
492                 _handle = PHYSFS_openWrite(cname);
493                 break;
494 
495             case append:
496                 _handle = PHYSFS_openAppend(cname);
497                 break;
498         }
499 
500         if(!_handle) {
501             throw new DefileException("Failed to open file " ~ fileName);
502         }
503 
504         _name = fileName;
505     }
506 
507     /++
508         A wrapper for PHYSFS_close.
509 
510         The destructor will close the file automatically, but sometimes it
511         is necessary to do so manaualy.
512     +/
513     void close()
514     {
515         if(_handle) {
516             PHYSFS_close(_handle);
517             _handle = null;
518         }
519     }
520 
521     /++
522         A wrapper for PHYSFS_flush.
523 
524         Flushes the files internal buffer. See the documentation for
525         PHYSFS_flush for details.
526 
527         Throws:
528             DefileException if an error occurs.
529     +/
530     void flush()
531     {
532         if(PHYSFS_flush(_handle) == 0) {
533             throw new DefileException("Failed to flush file " ~ _name);
534         }
535     }
536 
537     /++
538         A wrapper for PHYSFS_seek.
539 
540         Seeks from the beginning of the file to the specified position. See
541         the documentation for PHYSFS_seek for details.
542 
543         Params:
544             position    = The offset from the beginning of the file to move to.
545         Throws:
546             DefileException if an error occurs.
547     +/
548     void seek(size_t position)
549     {
550         if(PHYSFS_seek(_handle, position) == 0) {
551             throw new DefileException(format("Failed to seek to position %s in file %s", position, _name));
552         }
553     }
554 
555     /++
556         A wrapper for PHYSFS_tell.
557 
558         See the documentation for PHYSFS_tell for details.
559 
560         Returns:
561             The current file position.
562         Throws:
563             DefileException if an error occurs.
564     +/
565     size_t tell()
566     {
567         auto ret = PHYSFS_tell(_handle);
568         if(ret == -1) {
569             throw new DefileException("Failed to determine position in file " ~ _name);
570         }
571         return cast(size_t)ret;
572     }
573 
574     /++
575         A wrapper for PHYSFS_read.
576 
577         Reads data from a file. Note that the file must have been opened
578         with the OpenFor.Read flag set. See the documentation for PHYSFS_read
579         for details.
580 
581         Params:
582             buffer      = The byte buffer which will be used to store the data
583                           read from the file. If the buffer is null, it will be
584                           allocated. If it is too small to hold objSize * objCount
585                           bytes, it will be extended.
586             objSize     = The number of bytes to read at a time.
587             objCount    = The number of times to read objSize bytes.
588         Returns:
589             The total number of bytes read. Note that this differs from
590             PHYSFS_read, which returns the number of objects read.
591         Throws:
592             DefileException if an error occurs.
593     +/
594     size_t read(ref ubyte[] buffer, size_t objSize, size_t objCount)
595     {
596         size_t bytesToRead = objSize * objCount;
597         if(buffer.length == 0) {
598             buffer = new ubyte[ bytesToRead ];
599         } else if(buffer.length < bytesToRead) {
600             buffer.length += bytesToRead;
601         }
602 
603         auto ret = PHYSFS_read(_handle, buffer.ptr, cast(uint)objSize, cast(uint)objCount);
604         if(ret == -1) {
605             throw new DefileException("Failed to read from file " ~ _name);
606         }
607         return cast(size_t)ret * objSize;
608     }
609 
610     /++
611         A wrapper for PHYSFS_read.
612 
613         Reads data from a file. Note that the file must have been opened
614         with the OpenFor.Read flag set. See the documentation for PHYSFS_read
615         for details.
616 
617         Params:
618             ptr         = A pointer that will be used to store the objects read
619                         = from the file.
620             objSize     = The number of bytes to read at a time.
621             objCount    = The number of times to read objSize bytes.
622         Returns:
623             The total number of objects read.
624         Throws:
625             DefileException if an error occurs.
626     +/
627 
628     size_t read(void* ptr, size_t objSize, size_t objCount)
629     {
630         auto ret = PHYSFS_read(_handle, ptr, cast(uint)objSize, cast(uint)objCount);
631         if(ret == -1) {
632             throw new DefileException("Failed to read from file " ~ _name);
633         }
634         return cast(size_t)ret;
635     }
636 
637     /++
638         A wrapper for PHYSFS_write.
639 
640         Writes data to a file. Note that the file must have been opened
641         with the OpenFor.Write or OpenFor.Append flag set. See the
642         documentation for PHYSFS_write for details.
643 
644         Params:
645             buffer      = The buffer containing the bytes which will be written
646                           to the file.
647             objSize     = The number of bytes to write at a time.
648             objCount    = The number of times to write objSize bytes.
649         Returns:
650             The total number of bytes written. Note that this differs from
651             PHYSFS_read, which returns the number of objects written.
652         Throws:
653             DefileException if an error occurs.
654     +/
655     size_t write(const(ubyte)[] buffer, size_t objSize, size_t objCount)
656     {
657         auto ret = PHYSFS_write(_handle, buffer.ptr, cast(uint)objSize, cast(uint)objCount);
658         if(ret == -1) {
659             throw new DefileException("Failed to write to file " ~ _name);
660         }
661         return cast(size_t)ret * objSize;
662     }
663 
664     /++
665         A wrapper for PHYSFS_write.
666 
667         Writes data to a file. Note that the file must have been opened
668         with the OpenFor.Write or OpenFor.Append flag set. See the
669         documentation for PHYSFS_write for details.
670 
671         Params:
672             ptr         = A pointer to the object(s) that will be written to file.
673             objSize     = The number of bytes to write at a time.
674             objCount    = The number of times to write objSize bytes.
675         Returns:
676             The total number of objects written.
677         Throws:
678             DefileException if an error occurs.
679     +/
680     size_t write(const(void)* ptr, size_t objSize, size_t objCount)
681     {
682         auto ret = PHYSFS_write(_handle, ptr, cast(uint)objSize, cast(uint)objCount);
683         if(ret == -1) {
684             throw new DefileException("Failed to write to file " ~ _name);
685         }
686         return cast(size_t)ret;
687     }
688 
689     /++
690         A templated wrapper for the PHYSFS_readSLE/ULE* functions.
691 
692         This method only accepts values that are of any integral type except
693         byte and ubyte.
694 
695         Returns:
696             A value of type T in little endian byte order.
697         Throws:
698             DefileException if an error occurs.
699 
700     +/
701     T readLE(T)() if(isIntegral!T && !is(T == byte) && !is(T == ubyte))
702     {
703         int ret;
704         T val;
705 
706         static if(is(T == short)) {
707             ret = PHYSFS_readSLE16(_handle, &val);
708         } else static if(is(T == ushort)) {
709             ret = PHYSFS_readULE16(_handle, &val);
710         } else static if(is(T == int)) {
711             ret = PHYSFS_readSLE32(_handle, &val);
712         } else static if(is(T == uint)) {
713             ret = PHYSFS_readULE32(_handle, &val);
714         } else static if(is(T == long)) {
715             ret = PHYSFS_readSLE64(_handle, &val);
716         } else static if(is(T == ulong)) {
717             ret = PHYSFS_readULE64(_handle, &val);
718         } else {
719             static assert(0);
720         }
721 
722         if(ret == 0) {
723             throw new DefileException(format("Failed to read %s LE value from file %s",
724                     T.stringOf, _name));
725         }
726         return val;
727     }
728 
729     /++
730         A templated wrapper for the PHYSFS_readSBE/UBE* functions.
731 
732         This method only accepts values that are of any integral type except
733         byte and ubyte.
734 
735         Returns:
736             A value of type T in big endian byte order.
737         Throws:
738             DefileException if an error occurs.
739 
740     +/
741     T readBE(T)() if(isIntegral!T && !is(T == byte) && !is(T == ubyte))
742     {
743         int ret;
744         T val;
745 
746         static if(is(T == short)) {
747             ret = PHYSFS_readSBE16(_handle, &val);
748         } else static if(is(T == ushort)) {
749             ret = PHYSFS_readUBE16(_handle, &val);
750         } else static if(is(T == int)) {
751             ret = PHYSFS_readSBE32(_handle, &val);
752         } else static if(is(T == uint)) {
753             ret = PHYSFS_readUBE32(_handle, &val);
754         } else static if(is(T == long)) {
755             ret = PHYSFS_readSBE64(_handle, &val);
756         } else static if(is(T == ulong)) {
757             ret = PHYSFS_readUBE64(_handle, &val);
758         } else {
759             static assert(0);
760         }
761 
762         if(ret == 0) {
763             throw new DefileException(format("Failed to read %s BE value from file %s",
764                     T.stringOf, _name));
765         }
766         return val;
767     }
768 
769     /++
770         A templated wrapper for the PHYSFS_writeSLE/ULE* functions.
771 
772         This method only accepts values that are of any integral type except
773         byte and ubyte.
774 
775         Params:
776             val     = A value of type T which will be written to the file in
777                       little endian byte order.
778         Throws:
779             DefileException if an error occurs.
780 
781     +/
782     void writeLE(T)(T val) if(isIntegral!T && !is(T == byte) && !is(T == ubyte))
783     {
784         int ret;
785 
786         static if(is(T == short)) {
787             ret = PHYSFS_writeSLE16(_handle, val);
788         } else static if(is(T == ushort)) {
789             ret = PHYSFS_writeULE16(_handle, val);
790         } else static if(is(T == int)) {
791             ret = PHYSFS_writeSLE32(_handle, val);
792         } else static if(is(T == uint)) {
793             ret = PHYSFS_writeULE32(_handle, val);
794         } else static if(is(T == long)) {
795             ret = PHYSFS_writeSLE64(_handle, val);
796         } else static if(is(T == ulong)) {
797             ret = PHYSFS_writeULE64(_handle, val);
798         } else {
799             static assert(0);
800         }
801 
802         if(ret == 0) {
803             throw new DefileException(format("Failed to write %s LE value to file %s",
804                     T.stringOf, _name));
805         }
806     }
807 
808     /++
809         A templated wrapper for the PHYSFS_writeSBE/UBE* functions.
810 
811         This method only accepts values that are of any integral type except
812         byte and ubyte.
813 
814         Params:
815             val     = A value of type T which will be written to the file in
816                       big endian byte order.
817         Throws:
818             DefileException if an error occurs.
819 
820     +/
821     void writeBE(T)(T val) if(isIntegral!T && !is(T == byte) && !is(T == ubyte))
822     {
823         int ret;
824 
825         static if(is(T == short)) {
826             ret = PHYSFS_writeSBE16(_handle, val);
827         } else static if(is(T == ushort)) {
828             ret = PHYSFS_writeUBE16(_handle, val);
829         } else static if(is(T == int)) {
830             ret = PHYSFS_writeSBE32(_handle, val);
831         } else static if(is(T == uint)) {
832             ret = PHYSFS_writeUBE32(_handle, val);
833         } else static if(is(T == long)) {
834             ret = PHYSFS_writeSBE64(_handle, val);
835         } else static if(is(T == ulong)) {
836             ret = PHYSFS_writeUBE64(_handle, val);
837         } else {
838             static assert(0);
839         }
840 
841         if(ret == 0) {
842             throw new DefileException(format("Failed to write %s BE value to file %s",
843                     T.stringOf, _name));
844         }
845     }
846     /++
847         A wrapper for PHYSFS_fileLength.
848 
849         Returns:
850             The total size, in bytes, of the file.
851     +/
852     size_t length()
853     {
854         if(!_handle) return 0;
855 
856         auto len = PHYSFS_fileLength(_handle);
857         if(len == -1) {
858             throw new DefileException("Invalid length for file " ~ _name);
859         }
860         return cast(size_t)len;
861     }
862 
863     /++
864         A wrapper for PHYSFS_eof.
865 
866         Returns:
867             True if the end of file has been reached, false otherwise.
868     +/
869     bool eof()
870     {
871         if(!_handle) return true;
872         return PHYSFS_eof(_handle) > 0;
873     }
874 
875     /++
876         A wrapper for PHYSFS_setBuffer.
877 
878         Sets the size of the files internal buffer.
879 
880         Params:
881             size    = The new buffer size.
882         Throws:
883             DefileException if an error occurs.
884     +/
885     void bufferSize(size_t size)
886     {
887         assert(_handle);
888         if(PHYSFS_setBuffer(_handle, size) == 0) {
889             throw new DefileException("Failed to set buffer size for file " ~ _name);
890         }
891     }
892 
893 private:
894     static string _baseDir;
895     static string _writeDir;
896     string _name;
897     PHYSFS_File *_handle;
898 }