diff --git a/include/akgl/SDL_GameControllerDB.h b/include/akgl/SDL_GameControllerDB.h index d0de86c..fbfd383 100644 --- a/include/akgl/SDL_GameControllerDB.h +++ b/include/akgl/SDL_GameControllerDB.h @@ -1,7 +1,7 @@ #ifndef _SDL_GAMECONTROLLERDB_H_ #define _SDL_GAMECONTROLLERDB_H_ -// Taken from https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/refs/heads/master/gamecontrollerdb.txt on Thu May 21 09:41:48 PM EDT 2026 +// Taken from https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/refs/heads/master/gamecontrollerdb.txt on Sun May 24 09:44:15 AM EDT 2026 #define AKGL_SDL_GAMECONTROLLER_DB_LEN 2228 diff --git a/include/akgl/error.h b/include/akgl/error.h index 96e5c41..a1f0098 100644 --- a/include/akgl/error.h +++ b/include/akgl/error.h @@ -1,6 +1,17 @@ #ifndef _ERROR_H_ #define _ERROR_H_ +// This macro is used to silence warnings on string concatenation operations that may fail. +// e.g., combining two element of PATH_MAX into a string buffer of AKGL_STRING_MAX_LENGTH. +// We have to draw a line in the sand somewhere or we will just let our buffers grow forever +// to keep the compiler happy. +#define DISABLE_GCC_WARNING_FORMAT_TRUNCATION \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wformat-truncation\"") + +#define RESTORE_GCC_WARNINGS \ + _Pragma("GCC diagnostic pop") + #define AKGL_ERR_SDL AKERR_LAST_ERRNO_VALUE + 1 #endif // _ERROR_H_ diff --git a/include/akgl/staticstring.h b/include/akgl/staticstring.h index 6cb1d03..704a70c 100644 --- a/include/akgl/staticstring.h +++ b/include/akgl/staticstring.h @@ -3,8 +3,9 @@ #include "string.h" #include +#include -#define AKGL_MAX_STRING_LENGTH 256 +#define AKGL_MAX_STRING_LENGTH PATH_MAX typedef struct { @@ -13,5 +14,5 @@ typedef struct } akgl_String; akerr_ErrorContext AKERR_NOIGNORE *akgl_string_initialize(akgl_String *obj, char *init); - +akerr_ErrorContext AKERR_NOIGNORE *akgl_string_copy(akgl_String *src, akgl_String *dst, int count); #endif //_STRING_H_ diff --git a/include/akgl/tilemap.h b/include/akgl/tilemap.h index bbfd930..6f8a917 100644 --- a/include/akgl/tilemap.h +++ b/include/akgl/tilemap.h @@ -1,6 +1,7 @@ #ifndef _TILEMAP_H_ #define _TILEMAP_H_ +#include #include "actor.h" #include "staticstring.h" #include @@ -11,7 +12,7 @@ #define AKGL_TILEMAP_MAX_TILESETS 16 #define AKGL_TILEMAP_MAX_TILES_PER_IMAGE 65536 #define AKGL_TILEMAP_MAX_TILESET_NAME_SIZE 512 -#define AKGL_TILEMAP_MAX_TILESET_FILENAME_SIZE 512 +#define AKGL_TILEMAP_MAX_TILESET_FILENAME_SIZE PATH_MAX #define AKGL_TILEMAP_MAX_OBJECT_NAME_SIZE 512 #define AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER 128 @@ -112,11 +113,11 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_tilemap_property(json_t *obj, c akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_properties_string(json_t *obj, char *key, akgl_String **dest); akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_properties_integer(json_t *obj, char *key, int *dest); akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_compute_tileset_offsets(akgl_Tilemap *dest, int tilesetidx); -akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load_layer_objects(akgl_Tilemap *dest, json_t *root, int layerid); -akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load_layer_tile(akgl_Tilemap *dest, json_t *root, int layerid); -akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load_layers(akgl_Tilemap *dest, json_t *root); -akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load_tilesets_each(json_t *tileset, akgl_Tilemap *dest, int tsidx); -akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load_tilesets(akgl_Tilemap *dest, json_t *root); +akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load_layer_objects(akgl_Tilemap *dest, json_t *root, int layerid, akgl_String *dirname); +akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load_layer_tile(akgl_Tilemap *dest, json_t *root, int layerid, akgl_String *dirname); +akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load_layers(akgl_Tilemap *dest, json_t *root, akgl_String *dirname); +akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load_tilesets_each(json_t *tileset, akgl_Tilemap *dest, int tsidx, akgl_String *dirname); +akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load_tilesets(akgl_Tilemap *dest, json_t *root, akgl_String *dirname); akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_release(akgl_Tilemap *dest); akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_scale_actor(akgl_Tilemap *map, akgl_Actor *actor); diff --git a/include/akgl/util.h b/include/akgl/util.h index f59f0b0..ed28f49 100644 --- a/include/akgl/util.h +++ b/include/akgl/util.h @@ -2,6 +2,7 @@ #define _UTIL_H_ #include +#include typedef struct point { int x; @@ -22,6 +23,8 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_rectangle_points(RectanglePoints *dest, akerr_ErrorContext AKERR_NOIGNORE *akgl_collide_point_rectangle(point *p, RectanglePoints *r, bool *collide); akerr_ErrorContext AKERR_NOIGNORE *akgl_collide_rectangles(SDL_FRect *r1, SDL_FRect *r2, bool *collide); +akerr_ErrorContext AKERR_NOIGNORE *akgl_path_relative(char *root, char *path, akgl_String *dst); + // These are REALLY slow routines that are only useful in testing harnesses akerr_ErrorContext AKERR_NOIGNORE *akgl_compare_sdl_surfaces(SDL_Surface *s1, SDL_Surface *s2); akerr_ErrorContext AKERR_NOIGNORE *akgl_render_and_compare(SDL_Texture *t1, SDL_Texture *t2, int x, int y, int w, int h, char *writeout); diff --git a/src/staticstring.c b/src/staticstring.c index 0d3afc0..5c85fe6 100644 --- a/src/staticstring.c +++ b/src/staticstring.c @@ -1,5 +1,6 @@ #include #include +#include akerr_ErrorContext *akgl_string_initialize(akgl_String *obj, char *init) { @@ -13,3 +14,17 @@ akerr_ErrorContext *akgl_string_initialize(akgl_String *obj, char *init) obj->refcount = 1; SUCCEED_RETURN(errctx); } + +akerr_ErrorContext *akgl_string_copy(akgl_String *src, akgl_String *dst, int count) +{ + PREPARE_ERROR(e); + FAIL_ZERO_RETURN(e, src, AKERR_NULLPOINTER, "NULL argument"); + FAIL_ZERO_RETURN(e, dst, AKERR_NULLPOINTER, "NULL argument"); + if ( count == 0 ) { + count = AKGL_MAX_STRING_LENGTH; + } + if ( (char *)dst->data != strncpy((char *)&src->data, (char *)&dst->data, count) ) { + FAIL_RETURN(e, errno, "strncpy"); + } + SUCCEED_RETURN(e); +} diff --git a/src/tilemap.c b/src/tilemap.c index 395be42..59a30d8 100644 --- a/src/tilemap.c +++ b/src/tilemap.c @@ -1,9 +1,13 @@ +#include +#include + #include #include #include -#include #include + #include +#include #include #include @@ -13,6 +17,7 @@ #include #include + akerr_ErrorContext *akgl_get_json_tilemap_property(json_t *obj, char *key, char *type, json_t **dest) { PREPARE_ERROR(errctx); @@ -96,7 +101,7 @@ akerr_ErrorContext *akgl_get_json_properties_number(json_t *obj, char *key, floa SUCCEED_RETURN(errctx); } -akerr_ErrorContext *akgl_tilemap_load_tilesets_each(json_t *tileset, akgl_Tilemap *dest, int tsidx) +akerr_ErrorContext *akgl_tilemap_load_tilesets_each(json_t *tileset, akgl_Tilemap *dest, int tsidx, akgl_String *dirname) { PREPARE_ERROR(errctx); akgl_String *tmpstr = NULL; @@ -118,13 +123,15 @@ akerr_ErrorContext *akgl_tilemap_load_tilesets_each(json_t *tileset, akgl_Tilema ); CATCH(errctx, akgl_get_json_string_value((json_t *)tileset, "image", &tmpstr)); - + + DISABLE_GCC_WARNING_FORMAT_TRUNCATION snprintf((char *)&dest->tilesets[tsidx].imagefilename, AKGL_TILEMAP_MAX_TILESET_FILENAME_SIZE, - "%s%s", - SDL_GetBasePath(), + "%s/%s", + dirname->data, tmpstr->data ); + RESTORE_GCC_WARNINGS dest->tilesets[tsidx].texture = IMG_LoadTexture(renderer, (char *)&dest->tilesets[tsidx].imagefilename); FAIL_ZERO_BREAK(errctx, dest->tilesets[tsidx].texture, AKERR_NULLPOINTER, "Failed loading tileset image : %s", SDL_GetError()); @@ -190,7 +197,7 @@ akerr_ErrorContext *akgl_tilemap_compute_tileset_offsets(akgl_Tilemap *dest, int SUCCEED_RETURN(errctx); } -akerr_ErrorContext *akgl_tilemap_load_tilesets(akgl_Tilemap *dest, json_t *root) +akerr_ErrorContext *akgl_tilemap_load_tilesets(akgl_Tilemap *dest, json_t *root, akgl_String *dirname) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "Received NULL tilemap pointer"); @@ -205,7 +212,7 @@ akerr_ErrorContext *akgl_tilemap_load_tilesets(akgl_Tilemap *dest, json_t *root) CATCH(errctx, akgl_get_json_array_value(root, "tilesets", &tilesets)) for (i = 0; i < json_array_size((json_t *)tilesets); i++) { CATCH(errctx, akgl_get_json_array_index_object((json_t *)tilesets, i, &jstileset)); - CATCH(errctx, akgl_tilemap_load_tilesets_each(jstileset, dest, i)); + CATCH(errctx, akgl_tilemap_load_tilesets_each(jstileset, dest, i, dirname)); CATCH(errctx, akgl_tilemap_compute_tileset_offsets(dest, i)); dest->numtilesets += 1; } @@ -216,7 +223,7 @@ akerr_ErrorContext *akgl_tilemap_load_tilesets(akgl_Tilemap *dest, json_t *root) SUCCEED_RETURN(errctx); } -akerr_ErrorContext *akgl_tilemap_load_layer_object_actor(akgl_TilemapObject *curobj, json_t *layerdatavalue, int layerid) +akerr_ErrorContext *akgl_tilemap_load_layer_object_actor(akgl_TilemapObject *curobj, json_t *layerdatavalue, int layerid, akgl_String *dirname) { PREPARE_ERROR(errctx); akgl_String *tmpstr = NULL; @@ -260,7 +267,7 @@ akerr_ErrorContext *akgl_tilemap_load_layer_object_actor(akgl_TilemapObject *cur SUCCEED_RETURN(errctx); } -akerr_ErrorContext *akgl_tilemap_load_layer_objects(akgl_Tilemap *dest, json_t *root, int layerid) +akerr_ErrorContext *akgl_tilemap_load_layer_objects(akgl_Tilemap *dest, json_t *root, int layerid, akgl_String *dirname) { PREPARE_ERROR(errctx); json_t *layerdata = NULL; @@ -290,7 +297,7 @@ akerr_ErrorContext *akgl_tilemap_load_layer_objects(akgl_Tilemap *dest, json_t * CATCH(errctx, akgl_get_json_string_value((json_t *)layerdatavalue, "type", &tmpstr)); if ( strcmp(tmpstr->data, "actor") == 0 ) { - CATCH(errctx, akgl_tilemap_load_layer_object_actor(curobj, layerdatavalue, layerid)); + CATCH(errctx, akgl_tilemap_load_layer_object_actor(curobj, layerdatavalue, layerid, dirname)); } else if ( strcmp(tmpstr->data, "perspective") == 0 ) { curobj->visible = false; if ( strcmp((char *)curobj->name, "p_foreground") == 0 ) { @@ -313,7 +320,7 @@ akerr_ErrorContext *akgl_tilemap_load_layer_objects(akgl_Tilemap *dest, json_t * SUCCEED_RETURN(errctx); } -akerr_ErrorContext *akgl_tilemap_load_layer_tile(akgl_Tilemap *dest, json_t *root, int layerid) +akerr_ErrorContext *akgl_tilemap_load_layer_tile(akgl_Tilemap *dest, json_t *root, int layerid, akgl_String *dirname) { PREPARE_ERROR(errctx); json_t *layerdata = NULL; @@ -322,6 +329,7 @@ akerr_ErrorContext *akgl_tilemap_load_layer_tile(akgl_Tilemap *dest, json_t *roo FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL destination tilemap reference"); FAIL_ZERO_RETURN(errctx, root, AKERR_NULLPOINTER, "NULL tilemap root reference"); + FAIL_ZERO_RETURN(errctx, dirname, AKERR_NULLPOINTER, "dirname"); ATTEMPT { CATCH(errctx, akgl_get_json_integer_value(root, "height", &dest->layers[layerid].height)); @@ -341,7 +349,7 @@ akerr_ErrorContext *akgl_tilemap_load_layer_tile(akgl_Tilemap *dest, json_t *roo SUCCEED_RETURN(errctx); } -akerr_ErrorContext *akgl_tilemap_load_layer_image(akgl_Tilemap *dest, json_t *root, int layerid) +akerr_ErrorContext *akgl_tilemap_load_layer_image(akgl_Tilemap *dest, json_t *root, int layerid, akgl_String *dirname) { PREPARE_ERROR(errctx); akgl_String *tmpstr; @@ -349,18 +357,21 @@ akerr_ErrorContext *akgl_tilemap_load_layer_image(akgl_Tilemap *dest, json_t *ro FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL destination tilemap reference"); FAIL_ZERO_RETURN(errctx, root, AKERR_NULLPOINTER, "NULL tilemap root reference"); + FAIL_ZERO_RETURN(errctx, dirname, AKERR_NULLPOINTER, "dirname"); ATTEMPT { CATCH(errctx, akgl_heap_next_string(&tmpstr)); CATCH(errctx, akgl_heap_next_string(&fpath)); CATCH(errctx, akgl_get_json_string_value(root, "image", &tmpstr)); + DISABLE_GCC_WARNING_FORMAT_TRUNCATION snprintf((char *)&fpath->data, AKGL_TILEMAP_MAX_TILESET_FILENAME_SIZE, - "%s%s", - SDL_GetBasePath(), + "%s/%s", + dirname->data, tmpstr->data ); + RESTORE_GCC_WARNINGS dest->layers[layerid].texture = IMG_LoadTexture(renderer, (char *)fpath->data); FAIL_ZERO_BREAK(errctx, dest->layers[layerid].texture, AKGL_ERR_SDL, "%s", SDL_GetError()); @@ -375,11 +386,12 @@ akerr_ErrorContext *akgl_tilemap_load_layer_image(akgl_Tilemap *dest, json_t *ro SUCCEED_RETURN(errctx); } -akerr_ErrorContext *akgl_tilemap_load_layers(akgl_Tilemap *dest, json_t *root) +akerr_ErrorContext *akgl_tilemap_load_layers(akgl_Tilemap *dest, json_t *root, akgl_String *dirname) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "akgl_tilemap_load_layers received NULL tilemap pointer"); FAIL_ZERO_RETURN(errctx, root, AKERR_NULLPOINTER, "akgl_tilemap_load_layers received NULL json object pointer"); + FAIL_ZERO_RETURN(errctx, dirname, AKERR_NULLPOINTER, "dirname"); json_t *layers = NULL; json_t *layer = NULL; akgl_String *tmpstr = NULL; @@ -406,13 +418,13 @@ akerr_ErrorContext *akgl_tilemap_load_layers(akgl_Tilemap *dest, json_t *root) SDL_Log("Layer %d has type %s", layerid, tmpstr->data); if ( strncmp((char *)tmpstr->data, "objectgroup", strlen((char *)tmpstr->data)) == 0 ) { dest->layers[layerid].type = AKGL_TILEMAP_LAYER_TYPE_OBJECTS; - CATCH(errctx, akgl_tilemap_load_layer_objects((akgl_Tilemap *)dest, (json_t *)layer, layerid)); + CATCH(errctx, akgl_tilemap_load_layer_objects((akgl_Tilemap *)dest, (json_t *)layer, layerid, dirname)); } else if ( strncmp((char *)tmpstr->data, "tilelayer", strlen((char *)tmpstr->data)) == 0 ) { dest->layers[layerid].type = AKGL_TILEMAP_LAYER_TYPE_TILES; - CATCH(errctx, akgl_tilemap_load_layer_tile((akgl_Tilemap *)dest, (json_t *)layer, layerid)); + CATCH(errctx, akgl_tilemap_load_layer_tile((akgl_Tilemap *)dest, (json_t *)layer, layerid, dirname)); } else if ( strncmp((char *)tmpstr->data, "imagelayer", strlen((char *)tmpstr->data)) == 0 ) { dest->layers[layerid].type = AKGL_TILEMAP_LAYER_TYPE_IMAGE; - CATCH(errctx, akgl_tilemap_load_layer_image((akgl_Tilemap *)dest, (json_t *)layer, layerid)); + CATCH(errctx, akgl_tilemap_load_layer_image((akgl_Tilemap *)dest, (json_t *)layer, layerid, dirname)); } layer = NULL; layerid += 1; @@ -429,6 +441,7 @@ akerr_ErrorContext *akgl_tilemap_load(char *fname, akgl_Tilemap *dest) json_t *json = NULL; //akgl_String *tmpstr = NULL; json_error_t error; + akgl_String *dirnamestr = NULL; FAIL_ZERO_RETURN(errctx, fname, AKERR_NULLPOINTER, "load_tilemap received null filename"); FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "load_tilemap received null tilemap"); @@ -437,10 +450,13 @@ akerr_ErrorContext *akgl_tilemap_load(char *fname, akgl_Tilemap *dest) dest->p_foreground_scale = 1.0; dest->p_vanishing_scale = 1.0; + PASS(errctx, akgl_heap_next_string(&dirnamestr)); ATTEMPT { //CATCH(errctx, akgl_heap_next_string(&tmpstr)); //CATCH(errctx, akgl_string_initialize(tmpstr, NULL)); //SDL_snprintf(tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), fname); + CATCH(errctx, aksl_realpath(fname, (char *)&dirnamestr->data)); + dirname((char *)&dirnamestr->data); json = json_load_file(fname, 0, &error); FAIL_ZERO_BREAK( errctx, @@ -461,8 +477,8 @@ akerr_ErrorContext *akgl_tilemap_load(char *fname, akgl_Tilemap *dest) FAIL_RETURN(errctx, AKERR_OUTOFBOUNDS, "Map exceeds the maximum size"); } - CATCH(errctx, akgl_tilemap_load_layers((akgl_Tilemap *)dest, (json_t *)json)); - CATCH(errctx, akgl_tilemap_load_tilesets((akgl_Tilemap *)dest, (json_t *)json)); + CATCH(errctx, akgl_tilemap_load_layers((akgl_Tilemap *)dest, (json_t *)json, dirnamestr)); + CATCH(errctx, akgl_tilemap_load_tilesets((akgl_Tilemap *)dest, (json_t *)json, dirnamestr)); if ( dest->p_foreground_y && dest->p_vanishing_y ) { // How much bigger is the foreground vs the vanishing point? diff --git a/src/util.c b/src/util.c index 1c75a25..93763a6 100644 --- a/src/util.c +++ b/src/util.c @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -9,6 +10,72 @@ #include #include #include +#include + +#include + +akerr_ErrorContext *akgl_path_relative_root(char *root, char *path, akgl_String *dst) +{ + PREPARE_ERROR(e); + akgl_String *pathbuf; + akgl_String *strbuf; + char *result; + int rootlen; + int pathlen; + int count; + + FAIL_ZERO_RETURN(e, root, AKERR_NULLPOINTER, "NULL argument"); + FAIL_ZERO_RETURN(e, path, AKERR_NULLPOINTER, "NULL argument"); + FAIL_ZERO_RETURN(e, dst, AKERR_NULLPOINTER, "NULL argument"); + + PASS(e, akgl_heap_next_string(&strbuf)); + PASS(e, akgl_heap_next_string(&pathbuf)); + + ATTEMPT { + // Is it relative to the root? + rootlen = strlen(root); + pathlen = strlen(path); + if ( (rootlen + pathlen) >= AKGL_MAX_STRING_LENGTH ) { + FAIL_RETURN(e, AKERR_OUTOFBOUNDS, "Total path length (%d) is greater than maximum akgl_String length (%d)", (rootlen + pathlen), AKGL_MAX_STRING_LENGTH); + } + CATCH(e, aksl_sprintf(&count, (char *)&pathbuf->data, "%s/%s", root, path)); + CATCH(e, aksl_realpath((char *)&pathbuf->data, (char *)&strbuf->data)); + CATCH(e, akgl_string_copy(strbuf, dst, 0)); + } CLEANUP { + IGNORE(akgl_heap_release_string(strbuf)); + IGNORE(akgl_heap_release_string(pathbuf)); + } PROCESS(e) { + } FINISH(e, true); + SUCCEED_RETURN(e); +} + +akerr_ErrorContext *akgl_path_relative(char *root, char *path, akgl_String *dst) +{ + PREPARE_ERROR(e); + akgl_String *strbuf; + char *result; + + FAIL_ZERO_RETURN(e, root, AKERR_NULLPOINTER, "NULL argument"); + FAIL_ZERO_RETURN(e, path, AKERR_NULLPOINTER, "NULL argument"); + FAIL_ZERO_RETURN(e, dst, AKERR_NULLPOINTER, "NULL argument"); + + PASS(e, akgl_heap_next_string(&strbuf)); + + ATTEMPT { + // Is path relative to our current working directory? + CATCH(e, aksl_realpath(path, (char *)&strbuf->data)); + // Yes it is. strbuf->data contains the absolute path. + CATCH(e, akgl_string_copy(strbuf, dst, 0)); + } CLEANUP { + IGNORE(akgl_heap_release_string(strbuf)); + } PROCESS(e) { + } HANDLE(e, ENOENT) { + // Path is not relative to our current working directory + // Noop - execution proceeds after the break + return akgl_path_relative_root(root, path, dst); + } FINISH(e, true); + SUCCEED_RETURN(e); +} akerr_ErrorContext *akgl_rectangle_points(RectanglePoints *dest, SDL_FRect *rect) {