diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..1395ee4 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "deps/semver"] + path = deps/semver + url = git@github.com:h2non/semver.c.git diff --git a/CMakeLists.txt b/CMakeLists.txt index fefea0f..3a8e92a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,7 @@ add_custom_command( # Add include directories include_directories(${SDL3_INCLUDE_DIRS}) add_library(akgl SHARED + deps/semver/semver.c src/actor.c src/actor_state_string_names.c src/text.c @@ -55,7 +56,6 @@ add_library(akgl SHARED add_executable(charviewer util/charviewer.c) - add_executable(test_actor tests/actor.c) add_executable(test_bitmasks tests/bitmasks.c) add_executable(test_character tests/character.c) @@ -64,6 +64,7 @@ add_executable(test_sprite tests/sprite.c) add_executable(test_staticstring tests/staticstring.c) add_executable(test_tilemap tests/tilemap.c) add_executable(test_util tests/util.c) +add_executable(test_semver_unit deps/semver/semver_unit.c) add_test(NAME actor COMMAND test_actor) add_test(NAME bitmasks COMMAND test_bitmasks) add_test(NAME character COMMAND test_character) @@ -72,10 +73,12 @@ add_test(NAME sprite COMMAND test_sprite) add_test(NAME staticstring COMMAND test_staticstring) add_test(NAME tilemap COMMAND test_tilemap) add_test(NAME util COMMAND test_util) +add_test(NAME semver_unit COMMAND test_semver_unit) # Specify include directories for the library's headers (if applicable) target_include_directories(akgl PUBLIC - include/ + include/ + deps/semver/ ) target_link_libraries(akgl @@ -100,6 +103,7 @@ target_link_libraries(charviewer PRIVATE akerror::akerror akgl SDL3::SDL3 SDL3_t set(main_lib_dest "lib/akgl-${MY_LIBRARY_VERSION}") install(FILES ${CMAKE_CURRENT_BINARY_DIR}/akgl.pc DESTINATION "lib/pkgconfig/") install(TARGETS akgl DESTINATION "lib/") +install(FILES "deps/semver/semver.h" DESTINATION "include/") install(FILES "include/akgl/actor.h" DESTINATION "include/akgl/") install(FILES "include/akgl/types.h" DESTINATION "include/akgl/") install(FILES "include/akgl/text.h" DESTINATION "include/akgl/") diff --git a/deps/semver b/deps/semver new file mode 160000 index 0000000..bd1db23 --- /dev/null +++ b/deps/semver @@ -0,0 +1 @@ +Subproject commit bd1db234a68f305ed10268bd023df1ad672061d7 diff --git a/include/akgl/SDL_GameControllerDB.h b/include/akgl/SDL_GameControllerDB.h index 09109b2..87fc184 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 Fri May 8 10:15:45 PM EDT 2026 +// Taken from https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/refs/heads/master/gamecontrollerdb.txt on Fri May 8 11:02:47 PM EDT 2026 #define AKGL_SDL_GAMECONTROLLER_DB_LEN 2227 diff --git a/src/game.c b/src/game.c index b8601dc..f2318bc 100644 --- a/src/game.c +++ b/src/game.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -127,34 +128,61 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save(char *fpath) PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, fpath, AKERR_NULLPOINTER, "NULL file path"); - fp = fopen(fpath, "rb"); + fp = fopen(fpath, "wb"); + FAIL_ZERO_RETURN(errctx, fp, errno, "%s", fpath); + fwrite(&game, 1, sizeof(akgl_Game), fp); fclose(fp); - SUCCEED(errctx); // SUCCEED_NORETURN if in main(). + SUCCEED_RETURN(errctx); // SUCCEED_NORETURN if in main(). } akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load(char *fpath) { FILE *fp = NULL; - char version[32]; + char versionstr[32]; + semver_t current_version = {}; + semver_t compare_version = {}; PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, fpath, AKERR_NULLPOINTER, "NULL file path"); - fp = fopen(fpath, "rb"); - FAIL_ZERO_RETURN( - errctx, - (fread((void *)&version, 1, 32, fp) < 32), - AKERR_IO, + + ATTEMPT { + FAIL_NONZERO_BREAK( + errctx, + semver_parse((const char *)&game.version, ¤t_version), + AKERR_VALUE, + "Invalid semantic version in current game: %s", + (char *)¤t_version); + fp = fopen(fpath, "rb"); + FAIL_NONZERO_BREAK( + errctx, + (fread((void *)&versionstr, 1, 32, fp) < 32), + AKERR_IO, + "Corrupt save file"); + FAIL_NONZERO_BREAK( + errctx, + semver_parse((const char *)&versionstr, &compare_version), + AKERR_VALUE, + "Invalid semantic version in save game: %s", + (char *)&versionstr); + FAIL_ZERO_BREAK( + errctx, + semver_satisfies(compare_version, current_version, "^"), + AKERR_API, + "Incompatible save game version"); + rewind(fp); + FAIL_NONZERO_BREAK( + errctx, + (fread((void *)&game, 1, sizeof(akgl_Game), fp) < sizeof(akgl_Game)), + AKERR_IO, "Corrupt save file"); - FAIL_ZERO_RETURN( - errctx, - strncmp((char *)&game.version, (char *)&version, 32), - AKERR_API, - "Save game can not be loaded, it is from an incompatible version"); - rewind(fp); - FAIL_ZERO_RETURN( - errctx, - (fread((void *)&game, 1, sizeof(akgl_Game), fp) < sizeof(akgl_Game)), - AKERR_IO, - "Corrupt save file"); - SUCCEED(errctx); + } CLEANUP { + if ( fp != NULL ) { + fclose(fp); + } + semver_free(¤t_version); + semver_free(&compare_version); + } PROCESS(errctx) { + } FINISH(errctx, true); + + SUCCEED_RETURN(errctx); }