3 Commits

Author SHA1 Message Date
Marc fce1c2912c split the cli and the core functionnalities 2023-01-14 17:34:19 +01:00
Marc 0c2b3afa9b Fixed bug in fomod support 2023-01-13 20:04:32 +01:00
Marc 327486d897 Cleanup and improved conventions. 2023-01-13 20:04:00 +01:00
43 changed files with 887 additions and 755 deletions
+58 -22
View File
@@ -9,8 +9,8 @@ if(CMAKE_BUILD_TYPE MATCHES DEBUG)
endif()
set(CMAKE_C_FLAGS "-Wall -Wextra -pedantic -Wcast-qual -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -Wmaybe-uninitialized")
set(CMAKE_C_FLAGS_DEBUG "-g -fsanitize=address -Werror -O0")
set(CMAKE_C_FLAGS_RELEASE "-g")
set(CMAKE_C_FLAGS_DEBUG "-g -Werror -O0")
set(CMAKE_C_FLAGS_RELEASE "")
# generate the compile_commands for vscode / clang
set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "")
@@ -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 "")
+4
View File
@@ -0,0 +1,4 @@
* Add tests
* Add safe interrupt support
* Add a gui in an external binary
* make the CLI into a separate binary
+1 -1
View File
@@ -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' )
+3 -2
View File
@@ -2,17 +2,18 @@
## Function names
### shared functions
### header functions
function should start by the name of their module in camelCase followed by an underscore and the name of the function in camelCase
example:
steam_searchGames()
if the function has the same name as the module then it doesn't need to be repeated.
### static functions
there is no rule for now
all static functions should be in camelCase and they should not mention their module.
## Struct names
+62 -169
View File
@@ -8,16 +8,23 @@
#include <sys/wait.h>
#include <libaudit.h>
#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 <gameData.h>
#include <loadOrder.h>
#include <deploy.h>
#include <steam.h>
#include <install.h>
#include <getHome.h>
#include <steam.h>
#include <file.h>
#include <fomod.h>
#include <order.h>
#include <constants.h>
#include <deploy.h>
#include <errorType.h>
#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);
+2
View File
@@ -0,0 +1,2 @@
#define APP_NAME "mod-manager CLI"
#define VERSION "0.1"
+204
View File
@@ -0,0 +1,204 @@
#include "fomod.h"
#include <fomod.h>
#include <constants.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
//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;
}
+10
View File
@@ -0,0 +1,10 @@
#include <fomod.h>
/**
* @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);
-405
View File
@@ -1,405 +0,0 @@
#include <libxml/tree.h>
#include <libxml/parser.h>
#include <libxml/xmlstring.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdarg.h>
#include <unistd.h>
#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 - 1;
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 - 1;
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));
}
-28
View File
@@ -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
-35
View File
@@ -1,35 +0,0 @@
#ifndef __GROUP_H__
#define __GROUP_H__
#include <libxml/parser.h>
#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
-38
View File
@@ -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
+3 -8
View File
@@ -1,14 +1,12 @@
#ifndef __MAIN_H__
#define __MAIN_H__
#include <glib.h>
#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
+10
View File
@@ -0,0 +1,10 @@
#ifndef __DEPLOY_H__
#define __DEPLOY_H__
#include <glib.h>
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
+6
View File
@@ -0,0 +1,6 @@
#ifndef __ERROR_TYPE_H__
#define __ERROR_TYPE_H__
typedef enum { ERR_SUCCESS, ERR_FAILURE } error_t;
#endif
+5 -1
View File
@@ -2,8 +2,10 @@
#define __FILE_H__
#include <stdbool.h>
#include <glib.h>
#include <sys/types.h>
#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
+8 -13
View File
@@ -1,22 +1,17 @@
#ifndef __FOMOD_H__
#define __FOMOD_H__
#ifndef __PUBLIC_FOMOD_H__
#define __PUBLIC_FOMOD_H__
#include <stdbool.h>
#include "errorType.h"
#include <glib.h>
#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.
+71
View File
@@ -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
+5 -5
View File
@@ -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
+4 -1
View File
@@ -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
+3 -1
View File
@@ -1,7 +1,9 @@
#ifndef __INSTALL_H__
#define __INSTALL_H__
#include <stdlib.h>
#include "main.h"
#include <errorType.h>
#define INSTALLED_FLAG_FILE "__DO_NOT_REMOVE__"
/**
+1 -1
View File
@@ -2,7 +2,7 @@
#define __LOAD_ORDER_H__
#include <glib.h>
#include "main.h"
#include <errorType.h>
/**
* @brief find all plugins in the data folder of the game
+1 -1
View File
@@ -2,7 +2,7 @@
#define __ORDER_H__
#include <glib.h>
#include "main.h"
#include <errorType.h>
#define ORDER_FILE "__ORDER__"
+11 -4
View File
@@ -1,14 +1,12 @@
#ifndef __STEAM_H__
#define __STEAM_H__
#include "macro.h"
#include "main.h"
#include <stdbool.h>
#include <stddef.h>
#include <sys/types.h>
#include <unistd.h>
#include <glib.h>
#include <errorType.h>
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
+109
View File
@@ -0,0 +1,109 @@
#include <stdlib.h>
#include <sys/mount.h>
#include <unistd.h>
#include <errorType.h>
#include <deploy.h>
#include <steam.h>
#include <constants.h>
#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;
}
+18
View File
@@ -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;
}
+165
View File
@@ -0,0 +1,165 @@
#include <libxml/tree.h>
#include <libxml/parser.h>
#include <libxml/xmlstring.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdarg.h>
#include <unistd.h>
#include <fomod.h>
#include "file.h"
#include "libxml/globals.h"
#include <constants.h>
#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));
}
+11
View File
@@ -0,0 +1,11 @@
#ifndef __GROUP_H__
#define __GROUP_H__
#include <libxml/parser.h>
#include "xmlUtil.h"
#include <fomod.h>
int grp_parseGroup(xmlNodePtr groupNode, fomod_Group_t* group);
void grp_freeGroup(fomod_Group_t * group);
#endif
+61 -2
View File
@@ -1,5 +1,4 @@
#include "parser.h"
#include "fomodTypes.h"
#include <fomod.h>
#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;
}
@@ -3,9 +3,7 @@
#include <libxml/parser.h>
#include <stdbool.h>
typedef enum FOModOrder { ASC, DESC, ORD } fomod_Order_t;
#include <fomod.h>
/**
* @brief
+4 -4
View File
@@ -1,14 +1,14 @@
#include "getDataPath.h"
#include "main.h"
#include "steam.h"
#include <constants.h>
#include <gameData.h>
#include <steam.h>
#include <glib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdio.h>
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) {
View File
+4 -3
View File
@@ -4,8 +4,9 @@
#include <unistd.h>
#include <glib.h>
#include "install.h"
#include "main.h"
#include <install.h>
#include <constants.h>
#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;
+4 -3
View File
@@ -1,5 +1,6 @@
#include <constants.h>
#include "loadOrder.h"
#include "main.h"
#include "steam.h"
#include "file.h"
@@ -8,7 +9,7 @@
#include <sys/types.h>
#include <unistd.h>
#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;
}
View File
+4 -3
View File
@@ -3,7 +3,8 @@
#include "order.h"
//no function are declared in main it's just macros
#include "main.h"
#include <constants.h>
#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);
+1 -1
View File
@@ -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;
+33 -1
View File
@@ -1,7 +1,9 @@
#include "steam.h"
#include "macro.h"
#include "getHome.h"
#include "main.h"
#include <constants.h>
#include <unistd.h>
#include <stddef.h>
#include <stdio.h>
@@ -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;
}