This is a follow-up from a promise that I made in my tutorial video on designing an asset manager using SFML. That video can be found here.
This tutorial is centred around PhysFS++, a C++ wrapper for PhysicsFS. For the sake of the tutorial, it’s assumed that the reader is familiar with PhysicsFS and what it’s capable of. Described here is a workflow for simple use of the library. Anything more discrete is beyond the scope and will have to be ascertained by the reader on their own accord.
Lastly of note, PhysFS++ encapsulates PhysicsFS calls in a PhysFS namespace. The functions are global within this namespace and there are only a few classes that are provided. PhysFS++ further encapsulates key PhysicsFS constructs, notably those corresponding to archived files, into said classes that are derivatives of STL stream classes (a huge boon).
Like some other libraries, PhysicsFS requires explicit initialisation before it can be used. This is facilitated by a function named init. It takes one argument of type const char* and is semantically directed at a terminal invocation argument triaged through the much loved argv. However, this can be an empty string especially if you’re not expecting to handle terminal invocations. Thus, a very general call to init can be performed as such:
Right of the bat we have to mention a caveat here. From PhysFS++, there’s no way to assert the initialisation process of PhysicsFS. This is counter-intuitive to the upstream library which relies on the tried-and-true zero on failure, non-zero on success return values as sanity checks. Furthermore, while PhysFS++ implements C++ exceptions, the init function doesn’t throw any at all. Ostensibly, what one is left with is an unchecked call to init. Because one needs to compile PhysFS++ from the source, it is possible to modify the code, as I have done, to add a check to this call.
Once init has successfully completed, the next step is to mount an archive file into the virtual filesystem created by init. This is performed with the mount function. mount expects three arguments: the archive file on disk, a string specifying a mount point in the virtual filesystem, and a boolean which appends the mount point to the search path. One should place the archive file in the working directory for the binary file of their executable so PhysFS++ can see it. The second argument can be an empty string which would force root mounting, and the third can be true. Thus, for our example, if we assume we have an archive file named funphotos.zip on our disk in the binary’s working directory, we can issue a call to mount as such:
PhysFS::mount (“funphotos.zip”, “”, 1);
Unfortunately, as was the case with init, mount wraps an upstream function that adheres to the zero-or-nonzero return value paradigm that is outright dropped by PhysFS++ with no exception catering otherwise. Thus, if mount fails to mount the archive for whatever reason, it’ll be a little difficult to ascertain why; plan accordingly.
Assuming mount has returned properly, one can start to work with the files that are contained in the archive. At this point, you should begin working in the mentality of filesystem calls. It’s possible to have archive files with complex directory layouts which would require one to perform recursive searches. That being said, everything should be considered a file – even a directory. An analysis of the PhysicsFS and PhysFS++ APIs will provide for you the full breadth of your available capabilities so for the sake of brevity, only a select number of those will be touched here.
Enumeration of files in a directory can be performed with the enumerateFiles function. It takes as an argument a string which indicates the directory to use as a root for the enumeration. As a return it provides the caller with a vector of strings indicating the name of the file. For ease of use, a call to this to enumerate the files in root can be performed as such:
auto rootfiles = PhysFs::enumerateFiles (“/”);
To actually work with a file in the filesystem, one needs to use the PHYSFS_file ADT. This upstream ADT is encapsulated by one of three classes: PhysFS::ifstream, PhysFS::ostream, or PhysFS::fstream. Each of these is fantastic in that they encapsulate a PHYSFS_file ADT in a standard stream which means that one can now perform established stream operations on the file itself. Each class has its own use: fstream for reading files, ostream for writing to files, and ifstream for bidirectional file handling. In any case, any of these classes can be instantiated with a string argument that contains a fully qualified pathname to a file in the virtual filesystem that one wants to work with. For example, given that our virtual filesystem has a file named goofycats.png located at the path /textures/cats, we could instantiate a read file as such:
PhysFS::fstream gc (“/textures/cats/goofycats.png”);
Again, the underlying upstream call to open the file goes unchecked.
What you do from here is largely subjective relative to your program’s context. Say, for example (and this is actually pretty specific to my own use case), that you have a series of files of various formats in an archive that are being used in a video game program. The multimedia library you’re using should have ADTs that represent types of assets such as textures, fonts, etc. One could do some work to transpose the raw data of these streams into the aforementioned ADTs for use in the multimedia library. The following snippet of code, borrowed from one of my own projects, illustrates this. A PhysFS::fstream instance named f is created with a certain file. Following up is a char array named d is instantiated with the size of f. The read function of f is called to transpose the bytes from f into d. Then, d is used to instantiate an instance of sf::Music from SFML contained in a std::unordered_map:
This covers most the basic and general needs of users of PhysicsFS. Should you require anything else from it, you’d do well to read up on the Doxygen file of PhysicsFS or the source for PhysFS++. Lastly, when one is done with the library, you’ll need to call a deinitialization function called deinit: