Skip to content

Instantly share code, notes, and snippets.

@JeffM2501
Last active May 13, 2024 06:05
Show Gist options
  • Save JeffM2501/bd1092ce0eaedd26fe3ca60e1743ce40 to your computer and use it in GitHub Desktop.
Save JeffM2501/bd1092ce0eaedd26fe3ca60e1743ce40 to your computer and use it in GitHub Desktop.
Basic platform independent asset folder management for raylib.
/*******************************************************************************************
* Welcome to RLAssets!*
* RLAssets is licensed under an unmodified zlib/libpng license (View raylib.h for details)
*
* Copyright (c) 2020 Jeffery Myers
*
********************************************************************************************/
#include "RLAssets.h"
#include "raylib.h"
#include <map>
#include <string>
#include <vector>
#include <algorithm>
#if defined(_WIN32)
constexpr char PathDelim = '\\';
#else
constexpr char PathDelim = '/';
#endif // OSs
typedef struct
{
std::string RelativeName;
std::string PathOnDisk;
}rlas_AssetMeta;
typedef std::map<std::string, rlas_AssetMeta> MetaMap;
MetaMap AssetMap;
std::vector<std::string> AssetRootPaths;
std::string GetRelPath(const char* c)
{
std::string upperPath = c;
for (auto& c : upperPath)
c = toupper(c);
return upperPath;
}
void rlas_SetAssetRootPath(const char* path, bool relativeToApp)
{
AssetRootPaths.clear();
if (relativeToApp)
{
std::string appPath = rlas_GetApplicationBasePath();
if (path != nullptr)
{
appPath += path;
appPath += PathDelim;
}
rlas_AddAssetResourcePath(appPath.c_str());
}
else
{
rlas_AddAssetResourcePath(path);
}
}
const char* rlas_GetAssetRootPath()
{
if (AssetRootPaths.size() == 0)
return nullptr;
return AssetRootPaths[0].c_str();
}
void RecurseAddFiles(const std::string& root, const std::string& relRootPath)
{
int count = 0;
char** path = GetDirectoryFiles(root.c_str(), &count);
std::vector<std::string> subDirs;
for (int i = 0; i < count; ++i)
{
if (path[i] == nullptr || path[i][0] == '.')
continue;
std::string relPath = relRootPath + path[i];
std::string fullPath = root + path[i];
if (FileExists(fullPath.c_str()))
{
std::string upperPath = GetRelPath(relPath.c_str());
rlas_AssetMeta meta;
meta.RelativeName = relPath;
meta.PathOnDisk = fullPath;
AssetMap[upperPath] = meta;
}
else
{
subDirs.push_back(path[i]);
}
}
ClearDirectoryFiles();
for (auto subDir : subDirs)
{
RecurseAddFiles(root + subDir + PathDelim , relRootPath + subDir + "/");
}
}
void rlas_AddAssetResourcePath(const char* path)
{
if (path == nullptr)
return;
std::string root = path;
AssetRootPaths.emplace_back(root);
RecurseAddFiles(root, "/");
}
const char* rlas_GetAssetPath(const char* path)
{
MetaMap::iterator itr = AssetMap.find(GetRelPath(path));
if (itr == AssetMap.end())
return nullptr;
return itr->second.PathOnDisk.c_str();
}
int rlas_AppendPath(const char* path, const char* subpath, char* destination, int lenght)
{
std::string p = path;
std::string sp = subpath;
std::string result = p + PathDelim + sp;
if (result.size() > lenght - 1)
return -1;
#ifdef _WIN32
strncpy_s(destination, lenght, result.c_str(), result.size());
#else
strncpy(destination, result.c_str(), result.size());
#endif //_WIN32
destination[result.size()] = '\0';
return static_cast<int>(result.size());
}
int rlas_GetAssetsInPath(const char* path, bool recursive, char* results[])
{
int count = 0;
std::string upperPath = GetRelPath(path);
for (auto& asset : AssetMap)
{
if (asset.first.rfind(upperPath) == 0)
{
bool isFile = asset.first.find_first_of('/', upperPath.length()) > asset.first.size();
if (isFile || recursive)
{
if (results != nullptr)
results[count] = (char*)asset.second.RelativeName.c_str();
++count;
}
}
}
return count;
}
/*******************************************************************************************
* Welcome to RLAssets!*
* RLAssets is licensed under an unmodified zlib/libpng license (View raylib.h for details)
*
* Copyright (c) 2020 Jeffery Myers
*
********************************************************************************************/
#ifndef RLASSETS_H
#define RLASSETS_H
/// <summary>
/// Gets the application (exe) directory for the currently running program
/// </summary>
/// <returns>The path on disk in the current OSs format</returns>
const char* rlas_GetApplicationBasePath();
/// <summary>
/// adds a subpath (folder or file) to the specified path with the correct characters for the current OS
/// </summary>
/// <param name="path">the root path</param>
/// <param name="subpath">the sub path to append</param>
/// <param name="destination">the destination string</param>
/// <param name="lenght">the lenght of the destination string</param>
/// <returns>the lenght of the resulting combined path, -1 if the destination was not long enough</returns>
int rlas_AppendPath(const char* path, const char* subpath, char* destination, int lenght);
/// <summary>
/// Sets the initial asset path on disk
/// </summary>
/// <param name="path">The path to use as the asset root</param>
/// <param name="relativeToApp">When true the specified path will be used relative to the application root and should be in unix (/) format, when false the path specified is in the OSs format</param>
void rlas_SetAssetRootPath(const char* path, bool relativeToApp);
/// <summary>
/// Returns the top level asset root path
/// </summary>
/// <returns>The path on dis (OS format) of the inital asset root</returns>
const char* rlas_GetAssetRootPath();
/// <summary>
/// Adds an additional asset path to the search path for assets
/// The the specified path will be treated as '/' for relative paths
/// Any files that are duplicated in resource paths will be 'merged' into the virtual file structure and override older paths
/// </summary>
/// <param name="path">The resource path root to add</param>
void rlas_AddAssetResourcePath(const char* path);
/// <summary>
/// Gets the path on disk for an assets relative path
/// If multiple resource paths exist with the asset, the one added last will be returned.
/// </summary>
/// <param name="path">The relative path of the asset to look up</param>
/// <returns>The path on disk of the asset</returns>
const char* rlas_GetAssetPath(const char* path);
/// <summary>
/// Returns a list of all relative asset names in a resource path
/// Call once with results as NULL to get the count to allocate a result buffer large enough
/// Then call again with buffer to get results.
/// </summary>
/// <param name="path">The relative path to search </param>
/// <param name="includeSubDirectories">Search into subdirectories</param>
/// <param name="results">A pointer to a character array to store the results, when null not used.</param>
/// <returns>The number of asset items found</returns>
int rlas_GetAssetsInPath(const char* path, bool includeSubDirectories, char** results);
#endif //RLASSETS_H
/*******************************************************************************************
* Welcome to RLAssets!*
* RLAssets is licensed under an unmodified zlib/libpng license (View raylib.h for details)
*
* Copyright (c) 2020 Jeffery Myers
*
********************************************************************************************/
#include "RLAssets.h"
#include <string>
#if defined(_WIN32)
#include <windows.h>
constexpr char PathDelim = '\\';
#elif defined(__linux__)
#include <unistd.h>
constexpr char PathDelim = '/';
#elif defined(__APPLE__)
#include <sys/syslimits.h>
constexpr char PathDelim = '/';
#endif // OSs
std::string appDir;
const char* rlas_GetApplicationBasePath()
{
if (appDir.size() == 0)
{
appDir = "/"; // default for everyone to start out with
#if defined(_WIN32)
typedef DWORD(WINAPI* GetModuleFileNameFunc)(HANDLE, HMODULE, LPSTR, DWORD);
GetModuleFileNameFunc getModuleFileNameExWPtr = nullptr;
HMODULE lib = LoadLibrary(L"psapi.dll");
if (lib == nullptr)
{
appDir = "\\";
}
else
{
getModuleFileNameExWPtr = (GetModuleFileNameFunc)GetProcAddress(lib, "GetModuleFileNameExA");
if (getModuleFileNameExWPtr == nullptr)
{
appDir = "\\";
}
else
{
CHAR path[MAX_PATH];
int len = getModuleFileNameExWPtr(GetCurrentProcess(), nullptr, path, MAX_PATH);
if (len > 0)
{
for (int i = len; i >= 0; --i)
{
if (path[i] == '\\')
{
path[i + 1] = '\0';
i = -1;
}
}
appDir = path;
}
}
FreeLibrary(lib);
}
#elif defined(__linux__)
char path[4096 + 1];
uint32_t size = sizeof(path);
ssize_t len = readlink("/proc/self/exe", path, size);
if (len > 0)
{
for (int i = len; i >= 0; --i)
{
if (path[i] == '/')
{
path[i + 1] = '\0';
i = -1;
}
}
appDir = path;
}
#elif defined(__APPLE__)
char path[PATH_MAX + 1];
uint32_t size = sizeof(path);
if (_NSGetExecutablePath(path, &size) == 0)
{
int len = strlne(path);
for (int i = len; i >= 0; --i)
{
if (path[i] == '/')
{
path[i + 1] = '\0';
i = -1;
}
}
appDir = path;
}
#endif
}
return appDir.c_str();
}
@JeffM2501
Copy link
Author

This is a simple asset management system that works with raylib. It lets a game define a set of root folders to use for assets and then will managed all the OS specific bookeeping of getting paths correct. It has functions to find the currently running application on any OS, regardless of how it's started or what the current working directory is set to. It will also merge multiple roots into one virtual relative file system, so a game can have a base set of assets with the game program but an option set of "mod" files in other paths that override root assets.

Usage.

// define an asset root in the "resources" folder relative to the application
rlas_SetAssetRootPath("resources", true);

// load textures from relative paths
Texture tx1 = LoadTexture(rlas_GetAssetPath("/parrots.png"));
Texture tx2 = LoadTexture(rlas_GetAssetPath("/tiles/stone.png"));

// iterate assets in the virtual file system
int assetCount = rlas_GetAssetsInPath("/", true, nullptr);
char** buffer = (char**)malloc(assetCount * sizeof(char*));

rlas_GetAssetsInPath("/", true, buffer);

for (int i = 0; i < assetCount; ++i)
{
    PrintAsset (buffer[i]);
}
free(buffer);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment