From fce1c2912c019f5ff2c2ca9cdb80afdaab00a2a4 Mon Sep 17 00:00:00 2001 From: Marc Date: Sat, 14 Jan 2023 17:34:19 +0100 Subject: [PATCH] split the cli and the core functionnalities --- CMakeLists.txt | 76 ++-- PKGBUILD | 2 +- src/{main.c => cli/cli.c} | 231 ++++-------- src/cli/cli.h | 2 + src/cli/fomod.c | 204 +++++++++++ src/cli/fomod.h | 10 + src/fomod.c | 405 ---------------------- src/fomod/fomodTypes.h | 28 -- src/fomod/group.h | 35 -- src/fomod/parser.h | 38 -- src/{main.h => include/constants.h} | 11 +- src/include/deploy.h | 10 + src/include/errorType.h | 6 + src/{ => include}/file.h | 6 +- src/{ => include}/fomod.h | 21 +- src/include/fomodTypes.h | 71 ++++ src/{getDataPath.h => include/gameData.h} | 10 +- src/{ => include}/getHome.h | 5 +- src/{ => include}/install.h | 4 +- src/{ => include}/loadOrder.h | 2 +- src/{ => include}/order.h | 2 +- src/{ => include}/steam.h | 15 +- src/{ => lib}/archives.c | 0 src/{ => lib}/archives.h | 0 src/lib/deploy.c | 109 ++++++ src/{ => lib}/file.c | 18 + src/lib/fomod.c | 165 +++++++++ src/{ => lib}/fomod/group.c | 0 src/lib/fomod/group.h | 11 + src/{ => lib}/fomod/parser.c | 63 +++- src/{ => lib}/fomod/xmlUtil.c | 0 src/{ => lib}/fomod/xmlUtil.h | 4 +- src/{getDataPath.c => lib/gameData.c} | 8 +- src/{ => lib}/getHome.c | 0 src/{ => lib}/install.c | 7 +- src/{ => lib}/loadOrder.c | 7 +- src/{ => lib}/macro.h | 0 src/{ => lib}/order.c | 7 +- src/{ => lib}/overlayfs.c | 2 +- src/{ => lib}/overlayfs.h | 0 src/{ => lib}/steam.c | 34 +- 41 files changed, 878 insertions(+), 751 deletions(-) rename src/{main.c => cli/cli.c} (70%) create mode 100644 src/cli/cli.h create mode 100644 src/cli/fomod.c create mode 100644 src/cli/fomod.h delete mode 100644 src/fomod.c delete mode 100644 src/fomod/fomodTypes.h delete mode 100644 src/fomod/group.h delete mode 100644 src/fomod/parser.h rename src/{main.h => include/constants.h} (77%) create mode 100644 src/include/deploy.h create mode 100644 src/include/errorType.h rename src/{ => include}/file.h (94%) rename src/{ => include}/fomod.h (74%) create mode 100644 src/include/fomodTypes.h rename src/{getDataPath.h => include/gameData.h} (61%) rename src/{ => include}/getHome.h (71%) rename src/{ => include}/install.h (93%) rename src/{ => include}/loadOrder.h (98%) rename src/{ => include}/order.h (96%) rename src/{ => include}/steam.h (79%) rename src/{ => lib}/archives.c (100%) rename src/{ => lib}/archives.h (100%) create mode 100644 src/lib/deploy.c rename src/{ => lib}/file.c (90%) create mode 100644 src/lib/fomod.c rename src/{ => lib}/fomod/group.c (100%) create mode 100644 src/lib/fomod/group.h rename src/{ => lib}/fomod/parser.c (80%) rename src/{ => lib}/fomod/xmlUtil.c (100%) rename src/{ => lib}/fomod/xmlUtil.h (94%) rename src/{getDataPath.c => lib/gameData.c} (89%) rename src/{ => lib}/getHome.c (100%) rename src/{ => lib}/install.c (92%) rename src/{ => lib}/loadOrder.c (98%) rename src/{ => lib}/macro.h (100%) rename src/{ => lib}/order.c (93%) rename src/{ => lib}/overlayfs.c (95%) rename src/{ => lib}/overlayfs.h (100%) rename src/{ => lib}/steam.c (88%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9cea038..810d090 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,37 +19,73 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") project(mod-manager) -set(SOURCES - src/steam.c - src/loadOrder.c - src/install.c - src/overlayfs.c - src/getDataPath.c - src/getHome.c - src/file.c - src/order.c - src/archives.c - src/fomod.c - src/fomod/group.c - src/fomod/xmlUtil.c - src/fomod/parser.c +set(LIB_SOURCES + src/lib/steam.c + src/lib/loadOrder.c + src/lib/install.c + src/lib/overlayfs.c + src/lib/gameData.c + src/lib/getHome.c + src/lib/file.c + src/lib/order.c + src/lib/archives.c + src/lib/fomod.c + src/lib/deploy.c + src/lib/fomod/group.c + src/lib/fomod/xmlUtil.c + src/lib/fomod/parser.c +) + +# Headers files to export in the include folder +set(LIB_PUBLIC_HEADERS + src/include/constants.h + src/include/deploy.h + src/include/errorType.h + src/include/file.h + src/include/fomod.h + src/include/gameData.h + src/include/getHome.h + src/include/install.h + src/include/loadOrder.h + src/include/order.h + src/include/steam.h +) + +set(CLI_SOURCES + src/cli/cli.c + src/cli/fomod.c ) # add the executable -add_executable(mod-manager src/main.c ${SOURCES}) +add_executable(mod-manager ${CLI_SOURCES}) +add_library(libmod-manager SHARED ${LIB_SOURCES}) +set_target_properties(libmod-manager PROPERTIES PUBLIC_HEADER ${LIB_PUBLIC_HEADERS}) find_package(PkgConfig REQUIRED) pkg_search_module(GLIB REQUIRED glib-2.0) pkg_search_module(AUDIT REQUIRED audit) pkg_search_module(LIBXML REQUIRED libxml-2.0) -target_include_directories(mod-manager PRIVATE ${GLIB_INCLUDE_DIRS} ${LIBXML_INCLUDE_DIRS} ${AUDIT_INCLUDE_DIRS}) - +target_include_directories(libmod-manager PUBLIC src/include ${GLIB_INCLUDE_DIRS} ${LIBXML_INCLUDE_DIRS} ${AUDIT_INCLUDE_DIRS}) +target_link_libraries(libmod-manager ${GLIB_LDFLAGS}) +target_link_libraries(libmod-manager ${LIBXML_LIBRARIES}) +target_link_libraries(libmod-manager ${AUDIT_LIBRARIES}) +target_link_libraries(mod-manager libmod-manager) +target_include_directories(mod-manager PUBLIC src/include) target_link_libraries(mod-manager ${GLIB_LDFLAGS}) -target_link_libraries(mod-manager ${LIBXML_LIBRARIES}) -target_link_libraries(mod-manager ${AUDIT_LIBRARIES}) +target_include_directories(mod-manager PRIVATE ${GLIB_INCLUDE_DIRS}) -install(TARGETS mod-manager DESTINATION bin) + +install(TARGETS mod-manager OPTIONAL DESTINATION bin) + +install(TARGETS libmod-manager + LIBRARY DESTINATION lib + PUBLIC_HEADER DESTINATION include/mod-manager +) set_property(TARGET mod-manager PROPERTY C_STANDARD 23) +set_property(TARGET libmod-manager PROPERTY C_STANDARD 23) + +#avoid having liblibmod-manager +set_target_properties(libmod-manager PROPERTIES PREFIX "") diff --git a/PKGBUILD b/PKGBUILD index 71491fd..7d45c35 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -6,7 +6,7 @@ pkgdesc="a Mod manager for bethesda games" arch=('x86_64') # might work on other archs but i can not try url='https://gitlab.marcbarbier.fr/Marc/modmanager' license=('GPL2') -source=( 'git+https://gitlab.marcbarbier.fr/Marc/modmanager.git' ) +source=( 'git+https://github.com/Marc-Pierre-Barbier/ModManager.git' ) md5sums=( 'SKIP' ) optdepends=('fuse-overlayfs: special filesystem support' ) diff --git a/src/main.c b/src/cli/cli.c similarity index 70% rename from src/main.c rename to src/cli/cli.c index bf96014..1fcea14 100644 --- a/src/main.c +++ b/src/cli/cli.c @@ -8,16 +8,23 @@ #include #include -#include "getDataPath.h" -#include "loadOrder.h" -#include "overlayfs.h" -#include "install.h" -#include "getHome.h" -#include "steam.h" -#include "main.h" -#include "file.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cli.h" #include "fomod.h" -#include "order.h" + #define ENABLED_COLOR "\033[0;32m" #define DISABLED_COLOR "\033[0;31m" @@ -26,24 +33,6 @@ static bool isRoot() { return getuid() == 0; } -static GList * listFilesInFolder(const char * path) { - GList * list = NULL; - DIR *d; - struct dirent *dir; - d = opendir(path); - if (d) { - while ((dir = readdir(d)) != NULL) { - //removes .. & . from the list - if(strcmp(dir->d_name, "..") != 0 && strcmp(dir->d_name, ".") != 0) { - list = g_list_append(list, strdup(dir->d_name)); - } - } - closedir(d); - } - - return list; -} - static int usage() { printf("Use --list-games or -l to list compatible games\n"); @@ -67,36 +56,6 @@ static int usage() { return EXIT_FAILURE; } -static error_t validateAppId(const char * appIdStr) { - GHashTable * gamePaths; - error_t status = steam_searchGames(&gamePaths); - if(status == ERR_FAILURE) { - return ERR_FAILURE; - } - - char * strtoulSentinel; - //strtoul set EINVAL(after C99) if the string is invalid - unsigned long appid = strtoul(appIdStr, &strtoulSentinel, 10); - if(errno == EINVAL || strtoulSentinel == appIdStr) { - fprintf(stderr, "Appid has to be a valid number\n"); - return -1; - } - - int gameId = steam_gameIdFromAppId((int)appid); - if(gameId < 0) { - fprintf(stderr, "Game is not compatible\n"); - return -1; - } - - if(!g_hash_table_contains(gamePaths, &gameId)) { - fprintf(stderr, "Game not found\n"); - return -1; - } - - //no valid appid goes far enough to justify long - return (int)appid; -} - static int listGames(int argc, char **) { if(argc != 2) return usage(); @@ -125,7 +84,7 @@ static int add(int argc, char ** argv) { if(argc != 4) return usage(); const char * appIdStr = argv[2]; - int appid = validateAppId(appIdStr); + int appid = steam_parseAppId(appIdStr); if(appid < 0) { return EXIT_FAILURE; } @@ -137,14 +96,14 @@ static int add(int argc, char ** argv) { static int listAllMods(int argc, char ** argv) { if(argc != 3) return usage(); char * appIdStr = argv[2]; - int appid = validateAppId(appIdStr); + int appid = steam_parseAppId(appIdStr); if( appid < 0) { return EXIT_FAILURE; } //might crash if no mods were installed char * home = getHome(); - char * modFolder = g_build_filename(home, MANAGER_FILES, MOD_FOLDER_NAME, appIdStr, NULL); + char * modFolder = g_build_filename(home, MODLIB_WORKING_DIR, MOD_FOLDER_NAME, appIdStr, NULL); free(home); GList * mods = order_listMods(appid); @@ -183,7 +142,7 @@ static int listAllMods(int argc, char ** argv) { static int installAndUninstallMod(int argc, char ** argv, bool install) { if(argc != 4) return usage(); char * appIdStr = argv[2]; - int appid = validateAppId(appIdStr); + int appid = steam_parseAppId(appIdStr); if(appid < 0) { return EXIT_FAILURE; } @@ -199,9 +158,9 @@ static int installAndUninstallMod(int argc, char ** argv, bool install) { //might crash if no mods were installed char * home = getHome(); - char * modFolder = g_build_filename(home, MANAGER_FILES, MOD_FOLDER_NAME, appIdStr, NULL); + char * modFolder = g_build_filename(home, MODLIB_WORKING_DIR, MOD_FOLDER_NAME, appIdStr, NULL); free(home); - GList * mods = listFilesInFolder(modFolder); + GList * mods = order_listMods(appid); GList * modsFirstPointer = mods; for(unsigned long i = 0; i < modId; i++) { @@ -255,129 +214,63 @@ exit: return returnValue; } -static int deploy(int argc, char ** argv) { +static error_t cli_deploy(int argc, char ** argv) { if(argc != 3) return usage(); char * appIdStr = argv[2]; - int appid = validateAppId(appIdStr); + int appid = steam_parseAppId(appIdStr); if(appid < 0) { return EXIT_FAILURE; } - char * home = getHome(); - char * gameFolder = g_build_filename(home, MANAGER_FILES, GAME_FOLDER_NAME, appIdStr, NULL); + GList * ignoredMods = NULL; - if(access(gameFolder, F_OK) != 0) { - free(home); - g_free(gameFolder); - printf("Please run '%s --setup %d' first", APP_NAME, appid); - return EXIT_FAILURE; - } - - //unmount everything beforhand - //might crash if no mods were installed - char * modFolder = g_build_filename(home, MANAGER_FILES, MOD_FOLDER_NAME, appIdStr, NULL); - - GList * mods = listFilesInFolder(modFolder); - //since the priority is by alphabetical order - //and we need to bind the least important first - //and the most important last - //we have to reverse the list - mods = g_list_reverse(mods); - - //probably over allocating but this doesn't matter that much - char * modsToInstall[g_list_length(mods) + 2]; - int modCount = 0; - - while(true) { - if(mods == NULL)break; - char * modName = (char *)mods->data; - char * modPath = g_build_path("/", modFolder, modName, NULL); - char * modFlag = g_build_filename(modPath, INSTALLED_FLAG_FILE, NULL); - - //if the mod is not marked to be deployed then don't - if(access(modFlag, F_OK) != 0) { - printf("ignoring mod %s\n", modName); - mods = g_list_next(mods); - continue; - } - - //this is made to leave the first case empty so i can put lowerdir in it before feeding it to g_strjoinv - printf("Installing %s\n", modName); - modsToInstall[modCount] = modPath; - modCount += 1; - - mods = g_list_next(mods); - free(modFlag); - } - - g_free(modFolder); - - //const char * data = "lowerdir=gameFolder,gameFolder2,gameFolder3..." - modsToInstall[modCount] = gameFolder; - modsToInstall[modCount + 1] = NULL; - printf("Mounting the overlay\n"); - - char * dataFolder = NULL; - error_t error = getDataPath(appid, &dataFolder); - if(error != ERR_SUCCESS) { - free(home); + deploymentErrors_t errors = deploy(appIdStr, &ignoredMods); + switch(errors) { + //we prevalidate the appid so it is a bug + default: + case INVALID_APPID: + case BUG: + fprintf(stderr, "A bug was detected during the deployment\n"); return ERR_FAILURE; + case CANNOT_MOUNT: + fprintf(stderr, "Failed to mount the game files"); + return ERR_FAILURE; + case FUSE_NOT_INSTALLED: + fprintf(stderr, "This software requires fuse-overlayfs to be installed"); + return ERR_FAILURE; + case GAME_NOT_FOUND: + fprintf(stderr, "Could not find the game files"); + return ERR_FAILURE; + case OK: + printf("Success"); + return ERR_SUCCESS; } - - char * gameUpperDir = g_build_filename(home, MANAGER_FILES, GAME_UPPER_DIR_NAME, appIdStr, NULL); - char * gameWorkDir = g_build_filename(home, MANAGER_FILES, GAME_WORK_DIR_NAME, appIdStr, NULL); - free(home); - - //unmount the game folder - //DETACH + FORCE allow us to be sure it will be unload. - //it might crash / corrupt game file if the user do it while the game is running - //but it's still very unlikely - while(umount2(dataFolder, MNT_FORCE | MNT_DETACH) == 0); - overlay_errors_t status = overlay_mount(modsToInstall, dataFolder, gameUpperDir, gameWorkDir); - if(status == SUCESS) { - printf("Everything is ready, just launch the game\n"); - } else if(status == FAILURE) { - fprintf(stderr, "Could not mount the mods overlay\n"); - return EXIT_FAILURE; - } else if(status == NOT_INSTALLED) { - fprintf(stderr, "Please install fuse-overlayfs\n"); - return EXIT_FAILURE; - } else { - fprintf(stderr, "%d bug detected, please report this.\n", __LINE__); - return EXIT_FAILURE; - } - - g_free(dataFolder); - g_free(gameUpperDir); - g_free(gameWorkDir); - - return EXIT_SUCCESS; } static int setup(int argc, char ** argv) { if(argc != 3 ) return usage(); const char * appIdStr = argv[2]; - int appid = validateAppId(appIdStr); + int appid = steam_parseAppId(appIdStr); if(appid < 0) { return EXIT_FAILURE; } char * home = getHome(); - char * gameUpperDir = g_build_filename(home, MANAGER_FILES, GAME_UPPER_DIR_NAME, appIdStr, NULL); - char * gameWorkDir = g_build_filename(home, MANAGER_FILES, GAME_WORK_DIR_NAME, appIdStr, NULL); + char * gameUpperDir = g_build_filename(home, MODLIB_WORKING_DIR, GAME_UPPER_DIR_NAME, appIdStr, NULL); + char * gameWorkDir = g_build_filename(home, MODLIB_WORKING_DIR, GAME_WORK_DIR_NAME, appIdStr, NULL); g_mkdir_with_parents(gameUpperDir, 0755); g_mkdir_with_parents(gameWorkDir, 0755); free(gameUpperDir); free(gameWorkDir); char * dataFolder = NULL; - error_t error = getDataPath(appid, &dataFolder); + error_t error = gameData_getDataPath(appid, &dataFolder); if(error != ERR_SUCCESS) { return ERR_FAILURE; } - char * gameFolder = g_build_filename(home, MANAGER_FILES, GAME_FOLDER_NAME, appIdStr, NULL); + char * gameFolder = g_build_filename(home, MODLIB_WORKING_DIR, GAME_FOLDER_NAME, appIdStr, NULL); if(access(gameFolder, F_OK) == 0) { //if the game folder alredy exists just delete it //this will allow the removal of dlcs and language change @@ -410,13 +303,13 @@ static int setup(int argc, char ** argv) { static int unbind(int argc, char ** argv) { if(argc != 3 ) return usage(); const char * appIdStr = argv[2]; - int appid = validateAppId(appIdStr); + int appid = steam_parseAppId(appIdStr); if(appid < 0) { return EXIT_FAILURE; } char * dataFolder = NULL; - error_t error = getDataPath(appid, &dataFolder); + error_t error = gameData_getDataPath(appid, &dataFolder); if(error != ERR_SUCCESS) { return ERR_FAILURE; } @@ -430,7 +323,7 @@ static int unbind(int argc, char ** argv) { static int removeMod(int argc, char ** argv) { if(argc != 4) return usage(); const char * appIdStr = argv[2]; - int appid = validateAppId(appIdStr); + int appid = steam_parseAppId(appIdStr); if(appid < 0) { fprintf(stderr, "Invalid appid"); return EXIT_FAILURE; @@ -445,9 +338,9 @@ static int removeMod(int argc, char ** argv) { //might crash if no mods were installed char * home = getHome(); - char * modFolder = g_build_filename(home, MANAGER_FILES, MOD_FOLDER_NAME, appIdStr, NULL); + char * modFolder = g_build_filename(home, MODLIB_WORKING_DIR, MOD_FOLDER_NAME, appIdStr, NULL); free(home); - GList * mods = listFilesInFolder(modFolder); + GList * mods = order_listMods(appid); GList * modsFirstPointer = mods; for(unsigned long i = 0; i < modId; i++) { @@ -472,7 +365,7 @@ static int removeMod(int argc, char ** argv) { static int fomod(int argc, char ** argv) { if(argc != 4) return usage(); char * appIdStr = argv[2]; - int appid = validateAppId(appIdStr); + int appid = steam_parseAppId(appIdStr); if(appid < 0) { fprintf(stderr, "Invalid appid"); return EXIT_FAILURE; @@ -487,9 +380,9 @@ static int fomod(int argc, char ** argv) { //might crash if no mods were installed char * home = getHome(); - char * modFolder = g_build_filename(home, MANAGER_FILES, MOD_FOLDER_NAME, appIdStr, NULL); + char * modFolder = g_build_filename(home, MODLIB_WORKING_DIR, MOD_FOLDER_NAME, appIdStr, NULL); free(home); - GList * mods = listFilesInFolder(modFolder); + GList * mods = order_listMods(appid); GList * modsFirstPointer = mods; for(unsigned long i = 0; i < modId; i++) { @@ -523,7 +416,7 @@ static int fomod(int argc, char ** argv) { static int swapMod(int argc, char ** argv) { if(argc != 5) return usage(); char * appIdStr = argv[2]; - int appid = validateAppId(appIdStr); + int appid = steam_parseAppId(appIdStr); if(appid < 0) { fprintf(stderr, "Invalid appid"); return EXIT_FAILURE; @@ -551,7 +444,7 @@ static int printLoadOrder(int argc, char ** argv) { if(argc != 3) return usage(); char * appIdStr = argv[2]; - int appid = validateAppId(appIdStr); + int appid = steam_parseAppId(appIdStr); if(appid < 0) { fprintf(stderr, "Invalid appid"); return EXIT_FAILURE; @@ -580,7 +473,7 @@ static int printPlugins(int argc, char ** argv) { if(argc != 3) return usage(); char * appIdStr = argv[2]; - int appid = validateAppId(appIdStr); + int appid = steam_parseAppId(appIdStr); if(appid < 0) { fprintf(stderr, "Invalid appid"); return EXIT_FAILURE; @@ -613,7 +506,7 @@ int main(int argc, char ** argv) { int returnValue = EXIT_SUCCESS; char * home = getHome(); - char * configFolder = g_build_filename(home, MANAGER_FILES, NULL); + char * configFolder = g_build_filename(home, MODLIB_WORKING_DIR, NULL); free(home); if(access(configFolder, F_OK) != 0) { @@ -651,7 +544,7 @@ int main(int argc, char ** argv) { returnValue = installAndUninstallMod(argc, argv, false); else if(strcmp(argv[1], "--bind") == 0 || strcmp(argv[1], "-d") == 0) - returnValue = deploy(argc, argv); + returnValue = cli_deploy(argc, argv); else if(strcmp(argv[1], "--unbind") == 0) returnValue = unbind(argc, argv); diff --git a/src/cli/cli.h b/src/cli/cli.h new file mode 100644 index 0000000..4eb1bb0 --- /dev/null +++ b/src/cli/cli.h @@ -0,0 +1,2 @@ +#define APP_NAME "mod-manager CLI" +#define VERSION "0.1" diff --git a/src/cli/fomod.c b/src/cli/fomod.c new file mode 100644 index 0000000..61d072b --- /dev/null +++ b/src/cli/fomod.c @@ -0,0 +1,204 @@ +#include "fomod.h" +#include +#include +#include +#include +#include + +//match the definirion of gcompare func +static gint fomod_flagEqual(const fomod_Flag_t * a, const fomod_Flag_t * b) { + int nameCmp = strcmp(a->name, b->name); + if(nameCmp == 0) { + if(strcmp(a->value, b->value) == 0) + //is equal + return 0; + + return 1; + } + return nameCmp; +} + +static int getInputCount(const char * input) { + char buff[2]; + buff[1] = '\0'; + + int elementCount = 0; + bool prevWasValue = false; + + for(int i = 0; input[i] != '\0' && input[i] != '\n'; i++) { + buff[0] = input[i]; + + //if the character is a valid character + if(strstr("0123456789 ", buff) != NULL) { + if(input[i] == ' ') prevWasValue = false; + else if(!prevWasValue) { + prevWasValue = true; + elementCount++; + } + } else { + return -1; + } + + } + + return elementCount; +} + +static void fomod_printOptionsInOrder(fomod_Group_t group) { + for(int i = 0; i < group.pluginCount; i++) { + printf("%d, %s\n", i, group.plugins[i].name); + printf("%s\n", group.plugins[i].description); + } +} + +error_t fomod_installFOMod(const char * modFolder, const char * destination) { + //everything should be lowercase since we use casefold() before calling any install function + char * fomodFolder = g_build_path("/", modFolder, "fomod", NULL); + char * fomodFile = g_build_filename(fomodFolder, "moduleconfig.xml", NULL); + + printf("%s", fomodFile); + if(access(fomodFile, F_OK) != 0) { + fprintf(stderr, "FOMod file not found, are you sure this is a fomod mod ?\n"); + g_free(fomodFolder); + g_free(fomodFile); + return ERR_FAILURE; + } + + FOMod_t fomod; + int returnValue = parser_parseFOMod(fomodFile, &fomod); + if(returnValue == ERR_FAILURE) + return ERR_FAILURE; + + g_free(fomodFile); + + GList * flagList = NULL; + GList * pendingFileOperations = NULL; + + for(int i = 0; i < fomod.stepCount; i++) { + const FOModStep_t * step = &fomod.steps[i]; + + bool validFlags = true; + for(int flagId = 0; flagId < step->flagCount; flagId++) { + const GList * flagLink = g_list_find_custom(flagList, &step->requiredFlags[flagId], (GCompareFunc)fomod_flagEqual); + if(flagLink == NULL) { + validFlags = false; + break; + } + } + + if(!validFlags) continue; + + for(int groupId = 0; groupId < step->groupCount; groupId++ ) { + fomod_Group_t group = step->groups[groupId]; + + u_int8_t min; + u_int8_t max; + int inputCount = 0; + char *inputBuffer = NULL; + size_t bufferSize = 0; + + while(true) { + fomod_printOptionsInOrder(group); + switch(group.type) { + case ONE_ONLY: + printf("Select one :\n"); + min = 1; + max = 1; + break; + + case ANY: + printf("Select any (space separated) (leave empty for nothing) :\n"); + min = 0; + max = (u_int8_t)group.pluginCount; + break; + + case AT_LEAST_ONE: + printf("Select at least one (space separated) (leave empty for nothing) :\n"); + min = 1; + max = (u_int8_t)group.pluginCount; + break; + + case AT_MOST_ONE: + printf("Select one or none (space separated) (leave empty for nothing) :\n"); + min = 0; + max = 1; + break; + + case ALL: + printf("Press enter to continue\n"); + min = 0; + max = 0; + break; + default: + //never happen; + fprintf(stderr, "unexpected type please report this issue %d, %d", group.type, __LINE__); + return ERR_FAILURE; + } + + + + if(getline(&inputBuffer, &bufferSize, stdin) > 0) { + inputCount = getInputCount(inputBuffer); + if(inputCount <= max && inputCount >= min) { + printf("Continuing\n"); + break; + } + fprintf(stderr, "Invalid input\n"); + if(inputBuffer != NULL)free(inputBuffer); + inputBuffer = NULL; + } else { + //TODO: handle error + } + } + + //processing user input + + GRegex* regex = g_regex_new("\\s*", 0, 0, NULL); + char ** choices = g_regex_split(regex, inputBuffer, 0); + g_regex_unref(regex); + + for(int choiceId = 0; choices[choiceId] != NULL; choiceId++) { + //TODO: safer user input + int choice = atoi(choices[choiceId]); + fomod_Plugin_t plugin = group.plugins[choice]; + for(int flagId = 0; flagId < plugin.flagCount; flagId++) { + flagList = g_list_append(flagList, &plugin.flags[flagId]); + } + + //do the install + for(int pluginId = 0; pluginId < plugin.fileCount; pluginId++) { + const fomod_File_t * file = &plugin.files[pluginId]; + + fomod_File_t * fileCopy = malloc(sizeof(fomod_File_t)); + *fileCopy = *file; + + //changing pathes to lowercase since we used casefold and the pathes in the xml might not like it + char * destination = g_ascii_strdown(file->destination, -1); + fileCopy->destination = strdup(destination); + g_free(destination); + + char * source = g_ascii_strdown(file->source, -1); + fileCopy->source = strdup(source); + g_free(source); + + pendingFileOperations = g_list_append(pendingFileOperations, fileCopy); + } + } + + g_strfreev(choices); + if(inputBuffer != NULL)free(inputBuffer); + } + } + + //TODO: manage multiple files with the same name + + pendingFileOperations = fomod_processCondFiles(&fomod, flagList, pendingFileOperations); + fomod_processFileOperations(&pendingFileOperations, modFolder, destination); + + printf("FOMod successfully installed!\n"); + g_list_free(flagList); + fomod_freeFileOperations(pendingFileOperations); + fomod_freeFOMod(&fomod); + g_free(fomodFolder); + return ERR_SUCCESS; +} diff --git a/src/cli/fomod.h b/src/cli/fomod.h new file mode 100644 index 0000000..11b1894 --- /dev/null +++ b/src/cli/fomod.h @@ -0,0 +1,10 @@ +#include + +/** + * @brief Start a terminal based install process for fomod. + * + * @param modFolder folder of the fomod file. + * @param destination folder of the new mod that contains the result of the fomod process. + * @return int + */ +error_t fomod_installFOMod(const char * modFolder, const char * destination); diff --git a/src/fomod.c b/src/fomod.c deleted file mode 100644 index 66b4117..0000000 --- a/src/fomod.c +++ /dev/null @@ -1,405 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "fomod.h" -#include "file.h" -#include "fomod/fomodTypes.h" -#include "fomod/group.h" -#include "libxml/globals.h" -#include "main.h" - -static int getInputCount(const char * input) { - char buff[2]; - buff[1] = '\0'; - - int elementCount = 0; - bool prevWasValue = false; - - for(int i = 0; input[i] != '\0' && input[i] != '\n'; i++) { - buff[0] = input[i]; - - //if the character is a valid character - if(strstr("0123456789 ", buff) != NULL) { - if(input[i] == ' ') prevWasValue = false; - else if(!prevWasValue) { - prevWasValue = true; - elementCount++; - } - } else { - return -1; - } - - } - - return elementCount; -} - -static gint priorityCmp(gconstpointer a, gconstpointer b) { - const fomod_File_t * fileA = (const fomod_File_t *)a; - const fomod_File_t * fileB = (const fomod_File_t *)b; - - return fileB->priority - fileA->priority; -} - -static void fomod_printOptionsInOrder(fomod_Group_t group) { - for(int i = 0; i < group.pluginCount; i++) { - printf("%d, %s\n", i, group.plugins[i].name); - printf("%s\n", group.plugins[i].description); - } -} - -static gint fomod_flagEqual(const fomod_Flag_t * a, const fomod_Flag_t * b) { - int nameCmp = strcmp(a->name, b->name); - if(nameCmp == 0) { - if(strcmp(a->value, b->value) == 0) - //is equal - return 0; - - return 1; - } - return nameCmp; -} - -static int stepCmpAsc(const void * stepA, const void * stepB) { - const FOModStep_t * step1 = (const FOModStep_t *)stepA; - const FOModStep_t * step2 = (const FOModStep_t *)stepB; - return strcmp(step1->name, step2->name); -} - -static int stepCmpDesc(const void * stepA, const void * stepB) { - const FOModStep_t * step1 = (const FOModStep_t *)stepA; - const FOModStep_t * step2 = (const FOModStep_t *)stepB; - return 1 - strcmp(step1->name, step2->name); -} - - -static void fomod_sortSteps(FOMod_t * fomod) { - switch(fomod->stepOrder) { - case ASC: - qsort(fomod->steps, fomod->stepCount, sizeof(*fomod->steps), stepCmpAsc); - break; - case DESC: - qsort(fomod->steps, fomod->stepCount, sizeof(*fomod->steps), stepCmpDesc); - break; - case ORD: - //ord mean that we keep the curent order, so no need to sort anything. - break; - } -} - -static int fomod_groupCmpAsc(const void * stepA, const void * stepB) { - const fomod_Group_t * step1 = (const fomod_Group_t *)stepA; - const fomod_Group_t * step2 = (const fomod_Group_t *)stepB; - return strcmp(step1->name, step2->name); -} - -static int fomod_groupCmpDesc(const void * stepA, const void * stepB) { - const fomod_Group_t * step1 = (const fomod_Group_t *)stepA; - const fomod_Group_t * step2 = (const fomod_Group_t *)stepB; - return 1 - strcmp(step1->name, step2->name); -} - -static void fomod_sortGroup(fomod_Group_t * group) { - switch(group->order) { - case ASC: - qsort(group->plugins, group->pluginCount, sizeof(*group->plugins), fomod_groupCmpAsc); - break; - case DESC: - qsort(group->plugins, group->pluginCount, sizeof(*group->plugins), fomod_groupCmpDesc); - break; - case ORD: - //ord mean that we keep the curent order, so no need to sort anything. - break; - } -} - -//TODO: handle error -error_t fomod_processFileOperations(GList ** pendingFileOperations, const char * modFolder, const char * destination) { - //priority higher a less important and should be processed first. - *pendingFileOperations = g_list_sort(*pendingFileOperations, priorityCmp); - GList * currentFileOperation = *pendingFileOperations; - - while(currentFileOperation != NULL) { - //TODO: support destination - //no using link since priority is made to override files and link is annoying to deal with when overriding files. - const fomod_File_t * file = (const fomod_File_t *)currentFileOperation->data; - char * source = g_build_path("/", modFolder, file->source, NULL); - - //fix the / and \ windows - unix paths - xml_fixPath(source); - - int copyResult; - if(file->isFolder) { - copyResult = file_copy(source, destination, FILE_CP_NO_TARGET_DIR | FILE_CP_RECURSIVE); - } else { - copyResult = file_copy(source, destination, 0); - } - if(copyResult != EXIT_SUCCESS) { - fprintf(stderr, "Copy failed, some file might be corrupted\n"); - } - g_free(source); - - currentFileOperation = g_list_next(currentFileOperation); - } - return ERR_SUCCESS; -} - -GList * fomod_processCondFiles(const FOMod_t * fomod, GList * flagList, GList * pendingFileOperations) { - for(int condId = 0; condId < fomod->condFilesCount; condId++) { - const fomod_CondFile_t *condFile = &fomod->condFiles[condId]; - - bool areAllFlagsValid = true; - - //checking if all flags are valid - for(long flagId = 0; flagId < condFile->flagCount; flagId++) { - const GList * link = g_list_find_custom(flagList, &(condFile->requiredFlags[flagId]), (GCompareFunc)fomod_flagEqual); - if(link == NULL) { - areAllFlagsValid = false; - break; - } - } - - if(areAllFlagsValid) { - for(long fileId = 0; fileId < condFile->flagCount; fileId++) { - const fomod_File_t * file = &(condFile->files[fileId]); - - fomod_File_t * fileCopy = malloc(sizeof(*file)); - *fileCopy = *file; - - //changing pathes to lowercase since we used casefold and the pathes in the xml might not like it - char * destination = g_ascii_strdown(file->destination, -1); - fileCopy->destination = destination; - - char * source = g_ascii_strdown(file->source, -1); - fileCopy->source = source; - - pendingFileOperations = g_list_append(pendingFileOperations, fileCopy); - } - } - } - return pendingFileOperations; -} - -void fomod_freeFileOperations(GList * fileOperations) { - GList * fileOperationsStart = fileOperations; - while(fileOperations != NULL) { - fomod_File_t * file = (fomod_File_t *)fileOperations->data; - if(file->destination != NULL)free(file->destination); - if(file->source != NULL)free(file->source); - fileOperations = g_list_next(fileOperations); - } - - g_list_free_full(fileOperationsStart, free); -} - -error_t fomod_installFOMod(const char * modFolder, const char * destination) { - //everything should be lowercase since we use casefold() before calling any install function - char * fomodFolder = g_build_path("/", modFolder, "fomod", NULL); - char * fomodFile = g_build_filename(fomodFolder, "moduleconfig.xml", NULL); - - if(access(fomodFile, F_OK) != 0) { - fprintf(stderr, "FOMod file not found, are you sure this is a fomod mod ?\n"); - g_free(fomodFolder); - g_free(fomodFile); - return ERR_FAILURE; - } - - FOMod_t fomod; - int returnValue = parser_parseFOMod(fomodFile, &fomod); - if(returnValue == ERR_FAILURE) - return ERR_FAILURE; - - g_free(fomodFile); - - GList * flagList = NULL; - GList * pendingFileOperations = NULL; - - fomod_sortSteps(&fomod); - - for(int i = 0; i < fomod.stepCount; i++) { - const FOModStep_t * step = &fomod.steps[i]; - - bool validFlags = true; - for(int flagId = 0; flagId < step->flagCount; flagId++) { - const GList * flagLink = g_list_find_custom(flagList, &step->requiredFlags[flagId], (GCompareFunc)fomod_flagEqual); - if(flagLink == NULL) { - validFlags = false; - break; - } - } - - if(!validFlags) continue; - - for(int groupId = 0; groupId < step->groupCount; groupId++ ) { - fomod_Group_t group = step->groups[groupId]; - - fomod_sortGroup(&group); - - u_int8_t min; - u_int8_t max; - int inputCount = 0; - char *inputBuffer = NULL; - size_t bufferSize = 0; - - while(true) { - fomod_printOptionsInOrder(group); - switch(group.type) { - case ONE_ONLY: - printf("Select one :\n"); - min = 1; - max = 1; - break; - - case ANY: - printf("Select any (space separated) (leave empty for nothing) :\n"); - min = 0; - max = (u_int8_t)group.pluginCount; - break; - - case AT_LEAST_ONE: - printf("Select at least one (space separated) (leave empty for nothing) :\n"); - min = 1; - max = (u_int8_t)group.pluginCount; - break; - - case AT_MOST_ONE: - printf("Select one or none (space separated) (leave empty for nothing) :\n"); - min = 0; - max = 1; - break; - - case ALL: - printf("Press enter to continue\n"); - min = 0; - max = 0; - break; - default: - //never happen; - fprintf(stderr, "unexpected type please report this issue %d, %d", group.type, __LINE__); - return ERR_FAILURE; - } - - - - if(getline(&inputBuffer, &bufferSize, stdin) > 0) { - inputCount = getInputCount(inputBuffer); - if(inputCount <= max && inputCount >= min) { - printf("Continuing\n"); - break; - } - fprintf(stderr, "Invalid input\n"); - if(inputBuffer != NULL)free(inputBuffer); - inputBuffer = NULL; - } else { - //TODO: handle error - } - } - - //processing user input - - GRegex* regex = g_regex_new("\\s*", 0, 0, NULL); - char ** choices = g_regex_split(regex, inputBuffer, 0); - g_regex_unref(regex); - - for(int choiceId = 0; choices[choiceId] != NULL; choiceId++) { - //TODO: safer user input - int choice = atoi(choices[choiceId]); - fomod_Plugin_t plugin = group.plugins[choice]; - for(int flagId = 0; flagId < plugin.flagCount; flagId++) { - flagList = g_list_append(flagList, &plugin.flags[flagId]); - } - - //do the install - for(int pluginId = 0; pluginId < plugin.fileCount; pluginId++) { - const fomod_File_t * file = &plugin.files[pluginId]; - - fomod_File_t * fileCopy = malloc(sizeof(fomod_File_t)); - *fileCopy = *file; - - //changing pathes to lowercase since we used casefold and the pathes in the xml might not like it - char * destination = g_ascii_strdown(file->destination, -1); - fileCopy->destination = strdup(destination); - g_free(destination); - - char * source = g_ascii_strdown(file->source, -1); - fileCopy->source = strdup(source); - g_free(source); - - pendingFileOperations = g_list_append(pendingFileOperations, fileCopy); - } - } - - g_strfreev(choices); - if(inputBuffer != NULL)free(inputBuffer); - } - } - - //TODO: manage multiple files with the same name - - pendingFileOperations = fomod_processCondFiles(&fomod, flagList, pendingFileOperations); - fomod_processFileOperations(&pendingFileOperations, modFolder, destination); - - printf("FOMod successfully installed!\n"); - g_list_free(flagList); - fomod_freeFileOperations(pendingFileOperations); - fomod_freeFOMod(&fomod); - g_free(fomodFolder); - return ERR_SUCCESS; -} - - -void fomod_freeFOMod(FOMod_t * fomod) { - for(int i = 0; i < fomod->condFilesCount; i++) { - fomod_CondFile_t * condFile = &(fomod->condFiles[i]); - for(long fileId = 0; fileId < condFile->fileCount; fileId++) { - free(condFile->files[fileId].destination); - free(condFile->files[fileId].source); - } - - for(long flagId = 0; flagId < condFile->flagCount; flagId++) { - fomod_Flag_t * flag = &(condFile->requiredFlags[flagId]); - free(flag->name); - free(flag->value); - } - free(condFile->files); - free(condFile->requiredFlags); - } - free(fomod->condFiles); - free(fomod->moduleImage); - free(fomod->moduleName); - - int size = fomod_countUntilNull(fomod->requiredInstallFiles, sizeof(char **)); - for(int i = 0; i < size; i++) { - free(fomod->requiredInstallFiles[i]); - } - free(fomod->requiredInstallFiles); - - for(int i = 0; i < fomod->stepCount; i++) { - FOModStep_t * step = &fomod->steps[i]; - for(int groupId = 0; groupId < step->groupCount; groupId++) { - fomod_Group_t * group = &step->groups[groupId]; - grp_freeGroup(group); - } - for(int flagId = 0; flagId < step->flagCount; flagId++) { - fomod_Flag_t * flag = &(step->requiredFlags[flagId]); - free(flag->name); - free(flag->value); - } - free(step->groups); - free(step->requiredFlags); - free(step->name); - } - free(fomod->steps); - - //set every counter to zero and every pointer to null - memset(fomod, 0, sizeof(FOMod_t)); -} diff --git a/src/fomod/fomodTypes.h b/src/fomod/fomodTypes.h deleted file mode 100644 index aa20d41..0000000 --- a/src/fomod/fomodTypes.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef __FOMOD_TYPES_H__ -#define __FOMOD_TYPES_H__ - -#include "stdbool.h" -#include "xmlUtil.h" - -typedef struct fomod_Flag { - char * name; - char * value; -} fomod_Flag_t; - -typedef struct fomod_File { - char * source; - char * destination; - int priority; - bool isFolder; -} fomod_File_t; - - - -typedef struct fomod_CondFile { - fomod_Flag_t * requiredFlags; - unsigned int flagCount; - fomod_File_t * files; - unsigned int fileCount; -} fomod_CondFile_t; - -#endif diff --git a/src/fomod/group.h b/src/fomod/group.h deleted file mode 100644 index 6583186..0000000 --- a/src/fomod/group.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef __GROUP_H__ -#define __GROUP_H__ - -#include -#include "fomodTypes.h" -#include "xmlUtil.h" - -typedef enum GroupType_t { ONE_ONLY, ANY, AT_LEAST_ONE, AT_MOST_ONE, ALL } GroupType_t; -typedef enum TypeDescriptor { OPTIONAL, MAYBE_USABLE, NOT_USABLE, REQUIRED, RECOMMENDED } TypeDescriptor_t; - -typedef struct fomod_Plugin { - char * description; - char * image; - fomod_Flag_t * flags; - int flagCount; - fomod_File_t * files; - int fileCount; - TypeDescriptor_t type; - char * name; -} fomod_Plugin_t; - -//combine group and "plugins" -typedef struct fomod_Group { - fomod_Plugin_t * plugins; - int pluginCount; - GroupType_t type; - char * name; - fomod_Order_t order; -} fomod_Group_t; - -int grp_parseGroup(xmlNodePtr groupNode, fomod_Group_t* group); -void grp_freeGroup(fomod_Group_t * group); - - -#endif diff --git a/src/fomod/parser.h b/src/fomod/parser.h deleted file mode 100644 index 37ebce8..0000000 --- a/src/fomod/parser.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef __FOMOD_PARSER_H__ -#define __FOMOD_PARSER_H__ - - -#include "fomodTypes.h" -#include "group.h" -#include "../main.h" - -//combine installStep and optionalFileGroups -typedef struct FOModStep { - fomod_Order_t optionOrder; - fomod_Group_t * groups; - int groupCount; - char * name; - fomod_Flag_t * requiredFlags; - int flagCount; -} FOModStep_t; - -typedef struct FOMod { - char * moduleName; - char * moduleImage; - char ** requiredInstallFiles; - fomod_Order_t stepOrder; - FOModStep_t * steps; - int stepCount; - fomod_CondFile_t * condFiles; - int condFilesCount; -} FOMod_t; - -/** - * @brief parse a fomod xml file (found inside the mod folder /fomod/moduleconfig.xml (everything should be lowercase)) - * - * @param fomodFile path to the moduleconfig.xml - * @param fomod pointer to a new FOMod_t - */ -error_t parser_parseFOMod(const char * fomodFile, FOMod_t* fomod); - -#endif diff --git a/src/main.h b/src/include/constants.h similarity index 77% rename from src/main.h rename to src/include/constants.h index a563bd6..e6dc7e4 100644 --- a/src/main.h +++ b/src/include/constants.h @@ -1,14 +1,12 @@ #ifndef __MAIN_H__ #define __MAIN_H__ -#include - +#define MODLIB_NAME "mod-manager" //no function should ever be put here, only keep important macros and globals here -#define APP_NAME "mod-manager" //relative to home the url is not preprocessed so ../ might create some issues // in c "A" "B" is the same as "AB" -#define MANAGER_FILES ".local/share/" APP_NAME +#define MODLIB_WORKING_DIR ".local/share/" MODLIB_NAME #define MOD_FOLDER_NAME "MOD_FOLDER" //the folder in which the games file will be linked or copied #define GAME_FOLDER_NAME "GAME_FOLDER" @@ -17,8 +15,5 @@ //overlayfs temporary dir. #define GAME_WORK_DIR_NAME "WORK_DIRS" -#define VERSION "0.1" - -typedef enum { ERR_SUCCESS, ERR_FAILURE } error_t; - +#define MODLIB_VERSION "0.1" #endif diff --git a/src/include/deploy.h b/src/include/deploy.h new file mode 100644 index 0000000..40d9ebd --- /dev/null +++ b/src/include/deploy.h @@ -0,0 +1,10 @@ +#ifndef __DEPLOY_H__ +#define __DEPLOY_H__ + +#include + +typedef enum deploymentErrors { INVALID_APPID, GAME_NOT_FOUND, OK, CANNOT_MOUNT, FUSE_NOT_INSTALLED, BUG } deploymentErrors_t; + +[[nodiscard]] deploymentErrors_t deploy(char * appIdStr, GList ** ignoredMods); + +#endif diff --git a/src/include/errorType.h b/src/include/errorType.h new file mode 100644 index 0000000..1cba6f0 --- /dev/null +++ b/src/include/errorType.h @@ -0,0 +1,6 @@ +#ifndef __ERROR_TYPE_H__ +#define __ERROR_TYPE_H__ + +typedef enum { ERR_SUCCESS, ERR_FAILURE } error_t; + +#endif diff --git a/src/file.h b/src/include/file.h similarity index 94% rename from src/file.h rename to src/include/file.h index 57d5618..08973f6 100644 --- a/src/file.h +++ b/src/include/file.h @@ -2,8 +2,10 @@ #define __FILE_H__ #include +#include #include -#include "main.h" + +#include "constants.h" //valid copy flags @@ -62,4 +64,6 @@ const char * file_extractExtension(const char * filePath); */ const char * file_extractFileName(const char * filePath); + +GList * file_listFolderContent(const char * path); #endif diff --git a/src/fomod.h b/src/include/fomod.h similarity index 74% rename from src/fomod.h rename to src/include/fomod.h index abd70bd..0f5a343 100644 --- a/src/fomod.h +++ b/src/include/fomod.h @@ -1,22 +1,17 @@ -#ifndef __FOMOD_H__ -#define __FOMOD_H__ +#ifndef __PUBLIC_FOMOD_H__ +#define __PUBLIC_FOMOD_H__ -#include +#include "errorType.h" #include - -#include "main.h" -#include "fomod/parser.h" -#include "fomod/group.h" -#include "fomod/xmlUtil.h" +#include "fomodTypes.h" /** - * @brief Start a terminal based install process for fomod. + * @brief parse a fomod xml file (found inside the mod folder /fomod/moduleconfig.xml (everything should be lowercase)) * - * @param modFolder folder of the fomod file. - * @param destination folder of the new mod that contains the result of the fomod process. - * @return int + * @param fomodFile path to the moduleconfig.xml + * @param fomod pointer to a new FOMod_t */ -error_t fomod_installFOMod(const char * modFolder, const char * destination); +error_t parser_parseFOMod(const char * fomodFile, FOMod_t* fomod); /** * @brief In fomod there is file operations which depends on multiple flags this function find the ones that mach our current flags and append them to a list. diff --git a/src/include/fomodTypes.h b/src/include/fomodTypes.h new file mode 100644 index 0000000..00bd23e --- /dev/null +++ b/src/include/fomodTypes.h @@ -0,0 +1,71 @@ +#ifndef __FOMOD_TYPES_H__ +#define __FOMOD_TYPES_H__ + +#include "stdbool.h" + +typedef enum GroupType_t { ONE_ONLY, ANY, AT_LEAST_ONE, AT_MOST_ONE, ALL } GroupType_t; +typedef enum TypeDescriptor { OPTIONAL, MAYBE_USABLE, NOT_USABLE, REQUIRED, RECOMMENDED } TypeDescriptor_t; +typedef enum FOModOrder { ASC, DESC, ORD } fomod_Order_t; + +typedef struct fomod_Flag { + char * name; + char * value; +} fomod_Flag_t; + +typedef struct fomod_File { + char * source; + char * destination; + int priority; + bool isFolder; +} fomod_File_t; + +typedef struct fomod_CondFile { + fomod_Flag_t * requiredFlags; + unsigned int flagCount; + fomod_File_t * files; + unsigned int fileCount; +} fomod_CondFile_t; + +typedef struct fomod_Plugin { + char * description; + char * image; + fomod_Flag_t * flags; + int flagCount; + fomod_File_t * files; + int fileCount; + TypeDescriptor_t type; + char * name; +} fomod_Plugin_t; + +//combine group and "plugins" +typedef struct fomod_Group { + fomod_Plugin_t * plugins; + int pluginCount; + GroupType_t type; + char * name; + fomod_Order_t order; +} fomod_Group_t; + +//combine installStep and optionalFileGroups +typedef struct FOModStep { + fomod_Order_t optionOrder; + fomod_Group_t * groups; + int groupCount; + char * name; + fomod_Flag_t * requiredFlags; + int flagCount; +} FOModStep_t; + +typedef struct FOMod { + char * moduleName; + char * moduleImage; + char ** requiredInstallFiles; + fomod_Order_t stepOrder; + FOModStep_t * steps; + int stepCount; + fomod_CondFile_t * condFiles; + int condFilesCount; +} FOMod_t; + + +#endif diff --git a/src/getDataPath.h b/src/include/gameData.h similarity index 61% rename from src/getDataPath.h rename to src/include/gameData.h index b0dcca7..f4c8585 100644 --- a/src/getDataPath.h +++ b/src/include/gameData.h @@ -1,7 +1,8 @@ -#ifndef __DATA_PATH_H__ -#define __DATA_PATH_H__ +#ifndef __GAME_DATA_H__ +#define __GAME_DATA_H__ -#include "main.h" + +#include "errorType.h" /** * @brief Get the path to the data folder @@ -9,7 +10,6 @@ * @param destination pointer to a null char * variable. it's value will be allocated with malloc * @return error_t */ -error_t getDataPath(int appid, char ** destination); - +error_t gameData_getDataPath(int appid, char ** destination); #endif diff --git a/src/getHome.h b/src/include/getHome.h similarity index 71% rename from src/getHome.h rename to src/include/getHome.h index 9ce8b04..f4cd9fe 100644 --- a/src/getHome.h +++ b/src/include/getHome.h @@ -1,4 +1,5 @@ - +#ifndef __GET_HOME_H__ +#define __GET_HOME_H__ /** * @brief Get the user home dir regardless if he used sudo. @@ -6,3 +7,5 @@ * @return char* path to the home dir */ char * getHome(void); + +#endif diff --git a/src/install.h b/src/include/install.h similarity index 93% rename from src/install.h rename to src/include/install.h index ba11f6d..a305199 100644 --- a/src/install.h +++ b/src/include/install.h @@ -1,7 +1,9 @@ #ifndef __INSTALL_H__ #define __INSTALL_H__ + #include -#include "main.h" +#include + #define INSTALLED_FLAG_FILE "__DO_NOT_REMOVE__" /** diff --git a/src/loadOrder.h b/src/include/loadOrder.h similarity index 98% rename from src/loadOrder.h rename to src/include/loadOrder.h index 2f8f67e..a7f1558 100644 --- a/src/loadOrder.h +++ b/src/include/loadOrder.h @@ -2,7 +2,7 @@ #define __LOAD_ORDER_H__ #include -#include "main.h" +#include /** * @brief find all plugins in the data folder of the game diff --git a/src/order.h b/src/include/order.h similarity index 96% rename from src/order.h rename to src/include/order.h index 33f1812..49c1d98 100644 --- a/src/order.h +++ b/src/include/order.h @@ -2,7 +2,7 @@ #define __ORDER_H__ #include -#include "main.h" +#include #define ORDER_FILE "__ORDER__" diff --git a/src/steam.h b/src/include/steam.h similarity index 79% rename from src/steam.h rename to src/include/steam.h index a2c72b1..3728504 100644 --- a/src/steam.h +++ b/src/include/steam.h @@ -1,14 +1,12 @@ #ifndef __STEAM_H__ #define __STEAM_H__ -#include "macro.h" -#include "main.h" - #include #include #include #include #include +#include typedef struct steam_App { unsigned int appid; @@ -43,7 +41,8 @@ static const char * GAMES_NAMES[] = { "Morrowind" }; -_Static_assert(LEN(GAMES_APPIDS) == LEN(GAMES_NAMES), "Game APPIDS and Game Names doesn't match"); +_Static_assert(sizeof(GAMES_NAMES) / sizeof(GAMES_NAMES[0]) == sizeof(GAMES_APPIDS) / sizeof(GAMES_APPIDS[0]), + "Game APPIDS and Game Names doesn't match"); /** * @brief list all installed games and the paths to the game's files @@ -64,4 +63,12 @@ void steam_freeGameTable(void); */ int steam_gameIdFromAppId(u_int32_t appid); +/** + * @brief Parse the steamId string + * + * @param appIdStr steamId string + * @return int -1 if failures or an int containing the steamId + */ +int steam_parseAppId(const char * appIdStr); + #endif diff --git a/src/archives.c b/src/lib/archives.c similarity index 100% rename from src/archives.c rename to src/lib/archives.c diff --git a/src/archives.h b/src/lib/archives.h similarity index 100% rename from src/archives.h rename to src/lib/archives.h diff --git a/src/lib/deploy.c b/src/lib/deploy.c new file mode 100644 index 0000000..4620e46 --- /dev/null +++ b/src/lib/deploy.c @@ -0,0 +1,109 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include "gameData.h" +#include "getHome.h" +#include "file.h" +#include "order.h" +#include "overlayfs.h" +#include "install.h" + + + +deploymentErrors_t deploy(char * appIdStr, GList ** ignoredMods) { + int appid = steam_parseAppId(appIdStr); + if(appid < 0) { + return INVALID_APPID; + } + + char * home = getHome(); + char * gameFolder = g_build_filename(home, MODLIB_WORKING_DIR, GAME_FOLDER_NAME, appIdStr, NULL); + + //is the game present in the disk + if(access(gameFolder, F_OK) != 0) { + free(home); + g_free(gameFolder); + return GAME_NOT_FOUND; + } + + char * dataFolder = NULL; + error_t error = gameData_getDataPath(appid, &dataFolder); + if(error != ERR_SUCCESS) { + free(home); + return GAME_NOT_FOUND; + } + + //unmount everything beforhand + //might crash if no mods were installed + char * modFolder = g_build_filename(home, MODLIB_WORKING_DIR, MOD_FOLDER_NAME, appIdStr, NULL); + + GList * mods = order_listMods(appid); + //since the priority is by alphabetical order + //and we need to bind the least important first + //and the most important last + //we have to reverse the list + mods = g_list_reverse(mods); + + //probably over allocating but this doesn't matter that much + char * modsToInstall[g_list_length(mods) + 2]; + int modCount = 0; + + while(true) { + if(mods == NULL)break; + char * modName = (char *)mods->data; + char * modPath = g_build_path("/", modFolder, modName, NULL); + char * modFlag = g_build_filename(modPath, INSTALLED_FLAG_FILE, NULL); + + //if the mod is not marked to be deployed then don't + if(access(modFlag, F_OK) != 0) { + *ignoredMods = g_list_append(*ignoredMods, modName); + mods = g_list_next(mods); + continue; + } + + //this is made to leave the first case empty so i can put lowerdir in it before feeding it to g_strjoinv + modsToInstall[modCount] = modPath; + modCount += 1; + + mods = g_list_next(mods); + free(modFlag); + } + + g_free(modFolder); + + //const char * data = "lowerdir=gameFolder,gameFolder2,gameFolder3..." + modsToInstall[modCount] = gameFolder; + modsToInstall[modCount + 1] = NULL; + + char * gameUpperDir = g_build_filename(home, MODLIB_WORKING_DIR, GAME_UPPER_DIR_NAME, appIdStr, NULL); + char * gameWorkDir = g_build_filename(home, MODLIB_WORKING_DIR, GAME_WORK_DIR_NAME, appIdStr, NULL); + free(home); + + //unmount the game folder + //DETACH + FORCE allow us to be sure it will be unload. + //it might crash / corrupt game file if the user do it while the game is running + //but it's still very unlikely + while(umount2(dataFolder, MNT_FORCE | MNT_DETACH) == 0); + overlay_errors_t status = overlay_mount(modsToInstall, dataFolder, gameUpperDir, gameWorkDir); + if(status == SUCESS) { + return OK; + } else if(status == FAILURE) { + return CANNOT_MOUNT; + } else if(status == NOT_INSTALLED) { + return FUSE_NOT_INSTALLED; + } else { + return BUG; + } + + g_free(dataFolder); + g_free(gameUpperDir); + g_free(gameWorkDir); + + return EXIT_SUCCESS; +} diff --git a/src/file.c b/src/lib/file.c similarity index 90% rename from src/file.c rename to src/lib/file.c index 30d7ca6..aa4b21a 100644 --- a/src/file.c +++ b/src/lib/file.c @@ -150,3 +150,21 @@ const char * file_extractExtension(const char * filePath) { const char * file_extractFileName(const char * filePath) { return file_extractLastPart(filePath, '/'); } + +GList * file_listFolderContent(const char * path) { + GList * list = NULL; + DIR *d; + struct dirent *dir; + d = opendir(path); + if (d) { + while ((dir = readdir(d)) != NULL) { + //removes .. & . from the list + if(strcmp(dir->d_name, "..") != 0 && strcmp(dir->d_name, ".") != 0) { + list = g_list_append(list, strdup(dir->d_name)); + } + } + closedir(d); + } + + return list; +} diff --git a/src/lib/fomod.c b/src/lib/fomod.c new file mode 100644 index 0000000..5bee65c --- /dev/null +++ b/src/lib/fomod.c @@ -0,0 +1,165 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "file.h" +#include "libxml/globals.h" +#include + +#include "fomod/group.h" +#include "fomod/xmlUtil.h" + +static gint priorityCmp(gconstpointer a, gconstpointer b) { + const fomod_File_t * fileA = (const fomod_File_t *)a; + const fomod_File_t * fileB = (const fomod_File_t *)b; + + return fileB->priority - fileA->priority; +} + +//match the definirion of gcompare func +static gint fomod_flagEqual(const fomod_Flag_t * a, const fomod_Flag_t * b) { + int nameCmp = strcmp(a->name, b->name); + if(nameCmp == 0) { + if(strcmp(a->value, b->value) == 0) + //is equal + return 0; + + return 1; + } + return nameCmp; +} + +//TODO: handle error +error_t fomod_processFileOperations(GList ** pendingFileOperations, const char * modFolder, const char * destination) { + //priority higher a less important and should be processed first. + *pendingFileOperations = g_list_sort(*pendingFileOperations, priorityCmp); + GList * currentFileOperation = *pendingFileOperations; + + while(currentFileOperation != NULL) { + //TODO: support destination + //no using link since priority is made to override files and link is annoying to deal with when overriding files. + const fomod_File_t * file = (const fomod_File_t *)currentFileOperation->data; + char * source = g_build_path("/", modFolder, file->source, NULL); + + //fix the / and \ windows - unix paths + xml_fixPath(source); + + int copyResult; + if(file->isFolder) { + copyResult = file_copy(source, destination, FILE_CP_NO_TARGET_DIR | FILE_CP_RECURSIVE); + } else { + copyResult = file_copy(source, destination, 0); + } + if(copyResult != EXIT_SUCCESS) { + fprintf(stderr, "Copy failed, some file might be corrupted\n"); + } + g_free(source); + + currentFileOperation = g_list_next(currentFileOperation); + } + return ERR_SUCCESS; +} + +GList * fomod_processCondFiles(const FOMod_t * fomod, GList * flagList, GList * pendingFileOperations) { + for(int condId = 0; condId < fomod->condFilesCount; condId++) { + const fomod_CondFile_t *condFile = &fomod->condFiles[condId]; + + bool areAllFlagsValid = true; + + //checking if all flags are valid + for(long flagId = 0; flagId < condFile->flagCount; flagId++) { + const GList * link = g_list_find_custom(flagList, &(condFile->requiredFlags[flagId]), (GCompareFunc)fomod_flagEqual); + if(link == NULL) { + areAllFlagsValid = false; + break; + } + } + + if(areAllFlagsValid) { + for(long fileId = 0; fileId < condFile->flagCount; fileId++) { + const fomod_File_t * file = &(condFile->files[fileId]); + + fomod_File_t * fileCopy = malloc(sizeof(*file)); + *fileCopy = *file; + + //changing pathes to lowercase since we used casefold and the pathes in the xml might not like it + char * destination = g_ascii_strdown(file->destination, -1); + fileCopy->destination = destination; + + char * source = g_ascii_strdown(file->source, -1); + fileCopy->source = source; + + pendingFileOperations = g_list_append(pendingFileOperations, fileCopy); + } + } + } + return pendingFileOperations; +} + +void fomod_freeFileOperations(GList * fileOperations) { + GList * fileOperationsStart = fileOperations; + while(fileOperations != NULL) { + fomod_File_t * file = (fomod_File_t *)fileOperations->data; + if(file->destination != NULL)free(file->destination); + if(file->source != NULL)free(file->source); + fileOperations = g_list_next(fileOperations); + } + + g_list_free_full(fileOperationsStart, free); +} + +void fomod_freeFOMod(FOMod_t * fomod) { + for(int i = 0; i < fomod->condFilesCount; i++) { + fomod_CondFile_t * condFile = &(fomod->condFiles[i]); + for(long fileId = 0; fileId < condFile->fileCount; fileId++) { + free(condFile->files[fileId].destination); + free(condFile->files[fileId].source); + } + + for(long flagId = 0; flagId < condFile->flagCount; flagId++) { + fomod_Flag_t * flag = &(condFile->requiredFlags[flagId]); + free(flag->name); + free(flag->value); + } + free(condFile->files); + free(condFile->requiredFlags); + } + free(fomod->condFiles); + free(fomod->moduleImage); + free(fomod->moduleName); + + int size = fomod_countUntilNull(fomod->requiredInstallFiles, sizeof(char **)); + for(int i = 0; i < size; i++) { + free(fomod->requiredInstallFiles[i]); + } + free(fomod->requiredInstallFiles); + + for(int i = 0; i < fomod->stepCount; i++) { + FOModStep_t * step = &fomod->steps[i]; + for(int groupId = 0; groupId < step->groupCount; groupId++) { + fomod_Group_t * group = &step->groups[groupId]; + grp_freeGroup(group); + } + for(int flagId = 0; flagId < step->flagCount; flagId++) { + fomod_Flag_t * flag = &(step->requiredFlags[flagId]); + free(flag->name); + free(flag->value); + } + free(step->groups); + free(step->requiredFlags); + free(step->name); + } + free(fomod->steps); + + //set every counter to zero and every pointer to null + memset(fomod, 0, sizeof(FOMod_t)); +} diff --git a/src/fomod/group.c b/src/lib/fomod/group.c similarity index 100% rename from src/fomod/group.c rename to src/lib/fomod/group.c diff --git a/src/lib/fomod/group.h b/src/lib/fomod/group.h new file mode 100644 index 0000000..1197854 --- /dev/null +++ b/src/lib/fomod/group.h @@ -0,0 +1,11 @@ +#ifndef __GROUP_H__ +#define __GROUP_H__ + +#include +#include "xmlUtil.h" +#include + +int grp_parseGroup(xmlNodePtr groupNode, fomod_Group_t* group); +void grp_freeGroup(fomod_Group_t * group); + +#endif diff --git a/src/fomod/parser.c b/src/lib/fomod/parser.c similarity index 80% rename from src/fomod/parser.c rename to src/lib/fomod/parser.c index 0d71a7d..4f91827 100644 --- a/src/fomod/parser.c +++ b/src/lib/fomod/parser.c @@ -1,5 +1,4 @@ -#include "parser.h" -#include "fomodTypes.h" +#include #include "group.h" #include "libxml/tree.h" #include "xmlUtil.h" @@ -193,6 +192,58 @@ static int parseConditionalInstalls(xmlNodePtr node, FOMod_t * fomod) { return EXIT_SUCCESS; } +static int stepCmpAsc(const void * stepA, const void * stepB) { + const FOModStep_t * step1 = (const FOModStep_t *)stepA; + const FOModStep_t * step2 = (const FOModStep_t *)stepB; + return strcmp(step1->name, step2->name); +} + +static int stepCmpDesc(const void * stepA, const void * stepB) { + const FOModStep_t * step1 = (const FOModStep_t *)stepA; + const FOModStep_t * step2 = (const FOModStep_t *)stepB; + return 1 - strcmp(step1->name, step2->name); +} + +static void fomod_sortSteps(FOMod_t * fomod) { + switch(fomod->stepOrder) { + case ASC: + qsort(fomod->steps, fomod->stepCount, sizeof(*fomod->steps), stepCmpAsc); + break; + case DESC: + qsort(fomod->steps, fomod->stepCount, sizeof(*fomod->steps), stepCmpDesc); + break; + case ORD: + //ord mean that we keep the curent order, so no need to sort anything. + break; + } +} + +static int fomod_groupCmpAsc(const void * stepA, const void * stepB) { + const fomod_Group_t * step1 = (const fomod_Group_t *)stepA; + const fomod_Group_t * step2 = (const fomod_Group_t *)stepB; + return strcmp(step1->name, step2->name); +} + +static int fomod_groupCmpDesc(const void * stepA, const void * stepB) { + const fomod_Group_t * step1 = (const fomod_Group_t *)stepA; + const fomod_Group_t * step2 = (const fomod_Group_t *)stepB; + return 1 - strcmp(step1->name, step2->name); +} + +static void fomod_sortGroup(fomod_Group_t * group) { + switch(group->order) { + case ASC: + qsort(group->plugins, group->pluginCount, sizeof(*group->plugins), fomod_groupCmpAsc); + break; + case DESC: + qsort(group->plugins, group->pluginCount, sizeof(*group->plugins), fomod_groupCmpDesc); + break; + case ORD: + //ord mean that we keep the curent order, so no need to sort anything. + break; + } +} + error_t parser_parseFOMod(const char * fomodFile, FOMod_t* fomod) { xmlDocPtr doc; xmlNodePtr cur; @@ -281,6 +332,14 @@ error_t parser_parseFOMod(const char * fomodFile, FOMod_t* fomod) { cur = cur->next; } + //steps are in a specific order in fomod config. we need to make sure we respected this order + fomod_sortSteps(fomod); + + //same for the plugins inside the groups + for(int i = 0; i < fomod->stepCount; i++) + for(int j = 0; j < fomod->steps[i].groupCount; j++) + fomod_sortGroup(&(fomod->steps[i].groups[j])); + xmlFreeDoc(doc); return ERR_SUCCESS; } diff --git a/src/fomod/xmlUtil.c b/src/lib/fomod/xmlUtil.c similarity index 100% rename from src/fomod/xmlUtil.c rename to src/lib/fomod/xmlUtil.c diff --git a/src/fomod/xmlUtil.h b/src/lib/fomod/xmlUtil.h similarity index 94% rename from src/fomod/xmlUtil.h rename to src/lib/fomod/xmlUtil.h index 42af76f..81c5b64 100644 --- a/src/fomod/xmlUtil.h +++ b/src/lib/fomod/xmlUtil.h @@ -3,9 +3,7 @@ #include #include - - -typedef enum FOModOrder { ASC, DESC, ORD } fomod_Order_t; +#include /** * @brief diff --git a/src/getDataPath.c b/src/lib/gameData.c similarity index 89% rename from src/getDataPath.c rename to src/lib/gameData.c index 4ea3b56..f1db84a 100644 --- a/src/getDataPath.c +++ b/src/lib/gameData.c @@ -1,14 +1,14 @@ -#include "getDataPath.h" -#include "main.h" -#include "steam.h" +#include +#include +#include #include #include #include #include -error_t getDataPath(int appid, char ** destination) { +error_t gameData_getDataPath(int appid, char ** destination) { GHashTable * gamePaths; error_t status = steam_searchGames(&gamePaths); if(status == ERR_FAILURE) { diff --git a/src/getHome.c b/src/lib/getHome.c similarity index 100% rename from src/getHome.c rename to src/lib/getHome.c diff --git a/src/install.c b/src/lib/install.c similarity index 92% rename from src/install.c rename to src/lib/install.c index bbbddf8..a5c01f4 100644 --- a/src/install.c +++ b/src/lib/install.c @@ -4,8 +4,9 @@ #include #include -#include "install.h" -#include "main.h" +#include + +#include #include "archives.h" #include "file.h" @@ -17,7 +18,7 @@ error_t install_addMod(char * filePath, int appId) { goto exit; } - char * configFolder = g_build_filename(getenv("HOME"), MANAGER_FILES, NULL); + char * configFolder = g_build_filename(getenv("HOME"), MODLIB_WORKING_DIR, NULL); if(g_mkdir_with_parents(configFolder, 0755) < 0) { fprintf(stderr, "Could not create mod folder"); resultError = ERR_FAILURE; diff --git a/src/loadOrder.c b/src/lib/loadOrder.c similarity index 98% rename from src/loadOrder.c rename to src/lib/loadOrder.c index d3533b9..ee7db65 100644 --- a/src/loadOrder.c +++ b/src/lib/loadOrder.c @@ -1,5 +1,6 @@ +#include + #include "loadOrder.h" -#include "main.h" #include "steam.h" #include "file.h" @@ -8,7 +9,7 @@ #include #include -#include "getDataPath.h" +#include "gameData.h" //TODO: detect if the game is running //TODO: deploy the game @@ -21,7 +22,7 @@ error_t order_listPlugins(int appid, GList ** plugins) { sprintf(appidStr, "%d", appid); char * dataFolder = NULL; - error_t error = getDataPath(appid, &dataFolder); + error_t error = gameData_getDataPath(appid, &dataFolder); if(error != ERR_SUCCESS) { return ERR_FAILURE; } diff --git a/src/macro.h b/src/lib/macro.h similarity index 100% rename from src/macro.h rename to src/lib/macro.h diff --git a/src/order.c b/src/lib/order.c similarity index 93% rename from src/order.c rename to src/lib/order.c index 03d935a..379d144 100644 --- a/src/order.c +++ b/src/lib/order.c @@ -3,7 +3,8 @@ #include "order.h" //no function are declared in main it's just macros -#include "main.h" +#include + #include "getHome.h" typedef struct Mod { @@ -27,7 +28,7 @@ GList * order_listMods(int appid) { sprintf(appidStr, "%d", appid); char * home = getHome(); - char * modFolder = g_build_filename(home, MANAGER_FILES, MOD_FOLDER_NAME, appidStr, NULL); + char * modFolder = g_build_filename(home, MODLIB_WORKING_DIR, MOD_FOLDER_NAME, appidStr, NULL); free(home); GList * list = NULL; @@ -99,7 +100,7 @@ error_t order_swapPlace(int appid, int modIdA, int modIdB) { sprintf(appidStr, "%d", appid); char * home = getHome(); - char * modFolder = g_build_filename(home, MANAGER_FILES, MOD_FOLDER_NAME, appidStr, NULL); + char * modFolder = g_build_filename(home, MODLIB_WORKING_DIR, MOD_FOLDER_NAME, appidStr, NULL); free(home); GList * list = order_listMods(appid); diff --git a/src/overlayfs.c b/src/lib/overlayfs.c similarity index 95% rename from src/overlayfs.c rename to src/lib/overlayfs.c index 8ba7c2a..04c39bc 100644 --- a/src/overlayfs.c +++ b/src/lib/overlayfs.c @@ -16,7 +16,7 @@ overlay_errors_t overlay_mount(char ** sources, const char * dest, const char * int pid = fork(); if(pid == 0) { //exit is used to get the return value when using waitpid - exit(execl("/usr/bin/fuse-overlayfs", "/usr/bin/fuse-overlayfs", "-r", "-o", mountData, dest, NULL)); + exit(execl("/usr/bin/fuse-overlayfs", "/usr/bin/fuse-overlayfs", "-o", mountData, dest, NULL)); } else { int returnValue = 0; diff --git a/src/overlayfs.h b/src/lib/overlayfs.h similarity index 100% rename from src/overlayfs.h rename to src/lib/overlayfs.h diff --git a/src/steam.c b/src/lib/steam.c similarity index 88% rename from src/steam.c rename to src/lib/steam.c index f43e1fb..1ace2e8 100644 --- a/src/steam.c +++ b/src/lib/steam.c @@ -1,7 +1,9 @@ #include "steam.h" #include "macro.h" #include "getHome.h" -#include "main.h" + +#include + #include #include #include @@ -255,3 +257,33 @@ int steam_gameIdFromAppId(u_int32_t appid) { } return -1; } + +int steam_parseAppId(const char * appIdStr) { + GHashTable * gamePaths; + error_t status = steam_searchGames(&gamePaths); + if(status == ERR_FAILURE) { + return -1; + } + + char * strtoulSentinel; + //strtoul set EINVAL(after C99) if the string is invalid + unsigned long appid = strtoul(appIdStr, &strtoulSentinel, 10); + if(errno == EINVAL || strtoulSentinel == appIdStr) { + fprintf(stderr, "Appid has to be a valid number\n"); + return -1; + } + + int gameId = steam_gameIdFromAppId((int)appid); + if(gameId < 0) { + fprintf(stderr, "Game is not compatible\n"); + return -1; + } + + if(!g_hash_table_contains(gamePaths, &gameId)) { + fprintf(stderr, "Game not found\n"); + return -1; + } + + //no valid appid goes far enough to justify long + return (int)appid; +}