0

Abstract file management with PhysicsFS

As many games do, I would like to pack the assets of the demos in a ZIP file, instead of having thousands of files (scripts, models, images, etc…), which gives an impression of an unpolished product.

After looking on several available libraries (and considering developing my own library), I have decided to use PhysicsFS (github project here) As mentioned in it’s home page: PhysicsFS is a library to provide abstract access to various archives.

One of the mandatory requirements to meet was to access the demo assets “as always” in order to keep the compatibility with the demoeditor (meaning that the demoengine should be able to access directly to files, instead of opening them from a zipped file), so, I have been working on a FileManager class that allows you to decide how to access the files: using PhysicsFS or accessing directly the files.

I recommend you to check out this github project that I made to know how PhysicsFS works and how I implemented the FileManager class, in case you cannot wait, here you have the class 🙂

The next step is to include this file management in the demoengine, as soon as I do it, I’ll post the changes! (for sure this class needs to be changed after implementing it properly :P)

#pragma once

#include <string>
#include <string_view>
#include <vector>
#include <memory>

namespace Phoenix
{
	// Class File
	class File;
	using SP_File = std::shared_ptr<File>;

	class File final {
	public:
		std::string	m_filePath;	// Path used to avoid uploading the same file twice
		char*		m_fileData;	// File data in memory
		uint32_t	m_fileSize;	// File size in bytes

	public:
		File();
		virtual ~File();

		bool load(const std::string_view filePath, bool WorkingWithDataFolder, const std::string Password = "");
	};

	// Class File Manager
	class FileManager final {

	public:
		FileManager();
		~FileManager();

	public:

		void init(const char* argv0);	// Init PhysFS
		void deinit();					// Deinit PysFS and unmount any file
		const std::string getVersion() { return m_physfsVersion; };

		bool mountData(const std::string_view dataFilePath);	// Mount a Data file or folder
		void setPassword(const std::string_view password) { m_password = password; };		// Set a password for files
		void clearPassword() { m_password = ""; };

		void setCache(bool cacheEnabled) { m_fileCache = cacheEnabled; };
		SP_File loadFile(const std::string_view filePath);		// Load a file to memory
		void clear();	// delete all files from memory

		
	private:
		bool m_workingWithDataFolder;
		bool m_fileCache;				// If cache is disabled, file data will be refreshed even if the file exists

		std::string m_password;
		std::string m_physfsVersion;
	
	public:
		std::vector<SP_File>	m_files;	// file list
		uint32_t				m_mem = 0;	// Memory in bytes loaded to memory

	};
}
#include "FileManager.h"
#include <cstring>
#include <filesystem>
#include <fstream>
#include <memory>
#include <physfs.h>

namespace Phoenix
{
	FileManager::FileManager()
	{
		clear();
		m_workingWithDataFolder = true;
		m_fileCache = false;
	}
	FileManager::~FileManager()
	{
		deinit();
		clear();
	}
	void FileManager::init(const char* argv0)
	{
		static const char* mainArgv0 = argv0;
		deinit();
		PHYSFS_init(mainArgv0);

		// Update version
		PHYSFS_Version compiled;
		PHYSFS_VERSION(&compiled);

		m_physfsVersion = std::to_string(compiled.major) + "." + std::to_string(compiled.minor) + "." + std::to_string(compiled.patch);
	}

	void FileManager::deinit()
	{
		if (PHYSFS_isInit() != 0)
		{
			PHYSFS_deinit();
		}
	}

	bool FileManager::mountData(const std::string_view dataFilePath)
	{
		if (std::filesystem::is_directory(dataFilePath.data()))
		{
			m_workingWithDataFolder = true;
			return true;
		}
		else {
			if (0 == PHYSFS_mount((const char*)dataFilePath.data(), NULL, 0))
				return false;
			m_workingWithDataFolder = false;
		}
		return true;
	}

	SP_File FileManager::loadFile(const std::string_view filePath)
	{
		SP_File p_file;

		for (auto& pFile : m_files) {
			if (pFile->m_filePath.compare(filePath.data()) == 0)
				p_file = pFile;
		}

		if (p_file == nullptr) { // If the file has not been found, we need to load it for the first time
			SP_File new_file = std::make_shared<File>();
			if (new_file->load(filePath, m_workingWithDataFolder, m_password)) {
				m_files.push_back(new_file);
				m_mem += new_file->m_fileSize;
				p_file = new_file;
				printf("File lodaded ok!: %s\n", new_file->m_filePath.c_str());
			}
			else {
				printf("File not loaded: %s\n", filePath.data());
			}
		}
		else { // If the file is in cache we should not do anything, unless we have been told to upload it again
			if (!m_fileCache) {
				m_mem -= p_file->m_fileSize; // Decrease the overall memory
				if (p_file->load(filePath, m_workingWithDataFolder, m_password)) {
					m_mem += p_file->m_fileSize;
					printf("File force lodaded ok!: %s\n", p_file->m_filePath.c_str());
				}
				else {
					printf("File not loaded: %s\n", filePath.data());
				}
			}
			else {
				printf("File already in memory: %s\n", filePath.data());
			}
		}
		return p_file;
	}

	void FileManager::clear()
	{
		m_files.clear();
		m_mem = 0;
	}

	File::File()
		:
		m_filePath(""),
		m_fileData(nullptr),
		m_fileSize(0)
	{
	}

	File::~File()
	{
		if (m_fileData)
			free(m_fileData);
		m_fileData = nullptr;
	}

	bool File::load(const std::string_view filePath, bool WorkingWithDataFolder, const std::string Password)
	{
		// Init file data
		if (m_fileData != nullptr)
			free(m_fileData);
		m_filePath = filePath;
		m_fileSize = 0;

		if (WorkingWithDataFolder) {
			// Working with files
			// 
			// Open the stream to 'lock' the file.
			std::ifstream f(filePath.data(), std::ios::in | std::ios::binary);
			if (f) {
				m_fileSize = static_cast<uint32_t>(std::filesystem::file_size(filePath.data()));

				// Allocate storage for the buffer
				m_fileData = new char[m_fileSize + 1];

				// Read the whole file into the buffer.
				f.read(m_fileData, m_fileSize);
				m_fileData[m_fileSize] = '\0'; // add the terminator
				f.close();
				return true;
			}
			else
				return false;
		}
		else {
			std::string filePathWithPassword = filePath.data();

			if (!Password.empty())
				filePathWithPassword += std::string("$") + Password;

			PHYSFS_file* file = PHYSFS_openRead(filePathWithPassword.c_str());
			if (file == NULL) {
				return false;
			}

			m_fileSize = static_cast<int>(PHYSFS_fileLength(file));

			m_fileData = new char[m_fileSize + 1];
			PHYSFS_read(file, m_fileData, 1, static_cast<PHYSFS_uint32>(m_fileSize));
			m_fileData[m_fileSize] = '\0'; // add the terminator
			PHYSFS_close(file);
			return true;
		}
	}
}

Leave a Reply

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

spammer, go home! * Time limit is exhausted. Please reload the CAPTCHA.

This site uses Akismet to reduce spam. Learn how your comment data is processed.