PhysFS and raylib

PhysFS is a great library to allow you to use various types of archives as if they were part of a common filesystem. You can even use it with archives embedded in your executable, to make a complete ready to run executable, you know how it is, there is always someone out there that fails to extract an archive in the way you’d expect!

Another benefit of PhysFS is you can “mount” a sub directory to the PhysFS filesystem, that can be written to, however you can’t escape from that part of your filesystem, meaning for example you can have plugins that can only write to their own data directory and really CAN’T rm -rf / effectively helping to sandbox them.

While you can compile PhysFS as a shared or static library, you can also with a little care, simply include the C sources into your project. I chose to use the sources directly to make life easier when compiling the same app for different platform. Its nice I don’t have to hassle with windows just to make an exe for someone… Its simply a case of including the windows specific source instead of the *nix specific source, in your respective Makefiles.

Loading data from memory using raylib is something of a work in progress but its not so much of a chore to copy bits of the raylib source and adapt some of the loaders to load from memory (and some media you can actually just load from memory)

After initialising PhysFS, you need to “mount” some data so you can access it.

PHYSFS_mount("data.zip",NULL,1);

But just as easily you can mount from a file that’s already in memory.

PHYSFS_mountMemory(&gdataZipData, gdataZipSize, NULL, "data.zip", NULL, 1);

Because there are some boiler plate steps to loading media with PhysFS and likely you’ll need to adapt for loading a particular type of media, it makes sense to make your own load form phys functions.

Lets start simple and load some text from a archive – handy for shaders.

char* LoadTextFromPhys(const char* path)
{
    PHYSFS_File* ph = PHYSFS_openRead(path);
    unsigned int len = PHYSFS_fileLength(ph);
    char* buf = RL_CALLOC(1,len);
    PHYSFS_readBytes(ph, buf, len);
    PHYSFS_close(ph);
    return buf;
}

Once you have a file handle for your virtual file path, you will need the file length just so you know how much space to allocate for the text. Returning a pointer to this allocated memory will mean you need to free the buffer after you’re done with it…

Now there is a load text function we could for example load a shader like this

char* vstr = LoadTextFromPhys("data/simpleLight.vs");
char* fstr = LoadTextFromPhys("data/simpleLight.fs");
Shader shader = LoadShaderCode(vstr,fstr);
RL_FREE(vstr);  RL_FREE(fstr);

As there is a function to load shader code from memory for this media its fairly straight forward.

Loading a texture from an image is also quite straight forward to again thanks to the fact there is a load from memory functionality for images

Texture LoadTextureFromPhys(const char* path) 
{
    PHYSFS_File* ph = PHYSFS_openRead(path);
    unsigned int len = PHYSFS_fileLength(ph);
    unsigned char* buf = RL_MALLOC(len);
    PHYSFS_readBytes(ph, buf, len);
    
    Image img = loadImageFromMemory(buf, len);
    
    Texture t = LoadTextureFromImage(img);
    
    UnloadImage(img);
    RL_FREE(buf);
    PHYSFS_close(ph);
    
    return t;
}

As we’re disposing of a resource (used to create the texture) with this function things are a little more self contained the only thing to remember to free (UnLoad) is the texture, but that’s as usually anyway. Just as with the last function there is the routine of getting a handle to the resource and finding its size so we can allocate.

I ended up implementing an OBJ loader from PhysFS path too, that said I didn’t bother with the associated MTL files, and frankly the code is a bit messy, so I’ll save my blushes, as hopefully you should see that PhysFS isn’t as scary as it first looks, and you should be able to handle any new resource for yourself.

Enjoy!

Leave a Reply

Your email address will not be published. Required fields are marked *