diff --git a/CMakeLists.txt b/CMakeLists.txt index 109ff99..1305d88 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,16 +8,19 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") project(ModManager) # add the executable -add_executable(ModManager src/main.c src/steam.c src/install.c src/overlayfs.c src/getHome.c src/file.c) +add_executable(ModManager src/main.c src/steam.c src/install.c src/overlayfs.c src/getHome.c src/file.c src/fomod.c) add_compile_options(-Wall -Wextra -pedantic -Werror) find_package(PkgConfig REQUIRED) pkg_search_module(GLIB REQUIRED glib-2.0) pkg_search_module(audit REQUIRED libaudit) -target_include_directories(ModManager PRIVATE ${GLIB_INCLUDE_DIRS}) -target_link_libraries(ModManager ${AUDIT_LIBRARIES}) +pkg_search_module(LIBXML REQUIRED libxml-2.0) + +target_include_directories(ModManager PRIVATE ${GLIB_INCLUDE_DIRS} ${LIBXML_INCLUDE_DIRS}) add_compile_options(-fsanitize=address) add_link_options(-fsanitize=address) target_link_libraries(ModManager ${GLIB_LDFLAGS}) +target_link_libraries(ModManager ${AUDIT_LIBRARIES}) +target_link_libraries(ModManager ${LIBXML_LIBRARIES}) diff --git a/README.md b/README.md index fdd97c2..2016279 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Use --unbind or -u to rollback a deployment ## instructions and warnings about casefold: Casefold is and options in some linux filesystems that will make a folder (not the whole filesystem) case insensitive, this is really good for proton since it gives a small lookup performance boost and allow us to install mods without fearing having two file with the same name. -sadly i don't think overlayfs support it, but it might still be worth a try +sadly i don't think overlayfs support it, but it might still be worth a try. currently i haven't tested without casefold and mods might break because of that. diff --git a/src/fomod.c b/src/fomod.c new file mode 100644 index 0000000..1587aae --- /dev/null +++ b/src/fomod.c @@ -0,0 +1,299 @@ +#include "fomod.h" +#include +#include +#include +#include +#include +#include +#include + + +//TODO:cleanup prop +//and other strings + +int countUntilNull(void * pointers) { + int i = 0; + while(pointers != NULL) { + pointers++; + i++; + } + return i; +} + +GroupType_t getGroupType(const char * type) { + if(strcmp(type, "SelectAtLeastOne") == 0) { + return AT_LEAST_ONE; + } else if(strcmp(type, "SelectAtMostOne") == 0) { + return AT_MOST_ONE; + } else if(strcmp(type, "SelectExactlyOne") == 0) { + return ONE_ONLY; + } else if(strcmp(type, "SelectAll") == 0) { + return ALL; + } else if(strcmp(type, "SelectAny") == 0) { + return ANY; + } else { + return -1; + } +} + +FOModOrder_t getFOModOrder(const char * order) { + if(strcmp(order, "Explicit") == 0) { + return ORD; + } else if(strcmp(order, "Descending") == 0) { + return DESC; + } else if(strcmp(order, "Ascending") == 0) { + return ASC; + } + return -1; +} + +TypeDescriptor_t getDescriptor(const char * descriptor) { + if(strcmp(descriptor, "Optional") == 0) { + return OPTIONAL; + } else if(strcmp(descriptor, "Required") == 0) { + return REQUIRED; + } else if(strcmp(descriptor, "Recommended") == 0) { + return RECOMMENDED; + } else if(strcmp(descriptor, "NotUsable") == 0) { + return MAYBE_USABLE; + } else if(strcmp(descriptor, "NotUsable") == 0) { + return NOT_USABLE; + } else { + return -1; + } +} + +FOModGroup_t * parseGroup(xmlNodePtr groupNode) { + xmlNodePtr pluginsNode = groupNode->children; + FOModGroup_t * group = malloc(sizeof(FOModGroup_t)); + group->name = (char *) xmlGetProp( groupNode, (const xmlChar *) "name"); + group->type = getGroupType((char *) xmlGetProp(groupNode, (const xmlChar *) "type")); + group->order = getFOModOrder((char *) xmlGetProp(pluginsNode, (const xmlChar *) "order")); + FOModPlugin_t * plugins = NULL; + int pluginCount = 0; + + xmlNodePtr currentPlugin = pluginsNode->children; + while(currentPlugin != NULL) { + pluginCount += 1; + plugins = realloc(plugins, pluginCount * sizeof(FOModPlugin_t)); + FOModPlugin_t * plugin = &plugins[pluginCount - 1]; + + plugin->name = (char *) xmlGetProp(currentPlugin, (const xmlChar *) "name"); + + xmlNodePtr nodeElement = currentPlugin->children; + while(nodeElement != NULL) { + if(xmlStrcmp(nodeElement->name, (const xmlChar *) "description") == 0) { + plugin->description = (char *) xmlNodeGetContent(nodeElement); + } else if(xmlStrcmp(nodeElement->name, (const xmlChar *) "image") == 0) { + plugin->image = (char *) xmlGetProp(currentPlugin, (const xmlChar *) "path"); + } else if(xmlStrcmp(nodeElement->name, (const xmlChar *) "conditionFlags") == 0) { + FOModFlag_t * flags = NULL; + int flagCount = 0; + xmlNodePtr flagNode = nodeElement->children; + while(flagNode != NULL) { + flagCount += 1; + flags = realloc(flags, flagCount * sizeof(FOModFlag_t)); + + flags[flagCount - 1].name = (char *) xmlGetProp(nodeElement, (const xmlChar *) "name"); + flags[flagCount - 1].value = (char *) xmlNodeGetContent(nodeElement); + + flagNode = flagNode->next; + } + plugin->flags = flags; + } else if(xmlStrcmp(nodeElement->name, (const xmlChar *) "files") == 0) { + FOModFile_t * files = NULL; + int filesCount = 0; + xmlNodePtr flagNode = nodeElement->children; + while(flagNode != NULL) { + filesCount += 1; + + files = realloc(files, (filesCount + 1) * sizeof(FOModFile_t)); + files[filesCount - 1].destination = (char *) xmlGetProp(nodeElement, (const xmlChar *) "destination"); + files[filesCount - 1].source = (char *) xmlGetProp(nodeElement, (const xmlChar *) "source"); + files[filesCount - 1].priority = atoi((char *) xmlGetProp(nodeElement, (const xmlChar *) "priority")); + files[filesCount - 1].isFolder = xmlStrcmp(nodeElement->name, (const xmlChar *) "folder") == 0; + + flagNode = flagNode->next; + } + plugin->files = files; + } else if(xmlStrcmp(nodeElement->name, (const xmlChar *) "typeDescriptor") == 0) { + plugin->type = getDescriptor((char *) xmlGetProp(nodeElement->children, (const xmlChar *) "name")); + } + } + + + currentPlugin = currentPlugin->next; + } + + group->plugins = plugins; + group->pluginCount = pluginCount; + return group; +} + +FOModStep_t * parseInstallSteps(xmlNodePtr installStepsNode, int * stepCount) { + FOModStep_t * steps = NULL; + *stepCount = 0; + + xmlNodePtr stepNode = installStepsNode->children; + while(stepNode != NULL) { + stepCount += 1; + steps = realloc(steps, *stepCount * sizeof(FOModStep_t)); + + FOModStep_t * step = &steps[*stepCount - 1]; + step->name = (char *)xmlGetProp(stepNode, (const xmlChar *)"name"); + step->requiredFlags = NULL; + step->flagCount = 0; + + xmlNodePtr stepchildren = stepNode->children; + + while(stepchildren != NULL) { + if(xmlStrcmp(stepchildren->name, (const xmlChar *) "visible") == 0) { + xmlNodePtr requiredFlagsNode = stepchildren->children; + + while (requiredFlagsNode != NULL) { + step->flagCount += 1; + step->requiredFlags = realloc(step->requiredFlags, step->flagCount * sizeof(FOModFlag_t)); + FOModFlag_t * flag = &(step->requiredFlags[step->flagCount - 1]); + flag->name = (char *) xmlGetProp(requiredFlagsNode, (const xmlChar *) "flag"); + flag->value = (char *) xmlGetProp(requiredFlagsNode, (const xmlChar *) "value"); + + requiredFlagsNode = requiredFlagsNode->next; + } + + } else if(xmlStrcmp(stepchildren->name, (const xmlChar *) "optionalFileGroups") == 0) { + if(stepchildren != NULL) { + step->optionOrder = getFOModOrder((const char *)xmlGetProp(stepchildren, (const xmlChar *)"order")); + xmlNodePtr group = stepchildren->children; + step->groups = parseGroup(group); + } + } + + stepchildren = stepchildren->next; + } + + + + stepNode = stepNode->next; + } + return steps; +} + +void parseFOMod(const char * fomodFile, FOMod_t* fomod) { + xmlDocPtr doc; + xmlNodePtr cur; + + doc = xmlParseFile(fomodFile); + + if(doc == NULL) { + fprintf(stderr, "Document not parsed successfully"); + return; + } + + + cur = xmlDocGetRootElement(doc); + if(cur == NULL) { + fprintf(stderr, "emptyDocument"); + xmlFreeDoc(doc); + return; + } + + if(xmlStrcmp(cur->name, (const xmlChar *) "config") == 0) { + fprintf(stderr, "document of the wrong type, root node != config"); + } + + cur = cur->children; + while(cur != NULL) { + if(xmlStrcmp(cur->name, (const xmlChar *) "moduleName") == 0) { + //might cause some issues. when will c finally support utf-8 + fomod->moduleName = (char *)cur->content; + } else if(xmlStrcmp(cur->name, (const xmlChar *) "moduleImage") == 0) { + xmlChar * imagePath = xmlGetProp(cur, (const xmlChar *)"path"); + fomod->moduleImage = strdup((const char *)imagePath); + xmlFree(imagePath); + } else if(xmlStrcmp(cur->name, (const xmlChar *)"requiredInstallFiles") == 0) { + //TODO: support non empty destination. + xmlNodePtr requiredInstallFile = cur->children; + while(requiredInstallFile != NULL) { + int size = countUntilNull(fomod->requiredInstallFiles) + 2; + fomod->requiredInstallFiles = realloc(fomod->requiredInstallFiles, sizeof(char *) * size); + //ensure it is null terminated + fomod->requiredInstallFiles[size - 1] = NULL; + + //we remove all link with libxml2 + xmlChar * source = xmlGetProp(requiredInstallFile, (const xmlChar *)"source"); + fomod->requiredInstallFiles[size - 2] = strdup((const char *)source); + xmlFree(source); + + requiredInstallFile = cur->children; + } + } else if(xmlStrcmp(cur->name, (const xmlChar *)"installSteps") == 0) { + xmlChar * stepOrder = xmlGetProp(cur, (xmlChar *)"order"); + fomod->stepOrder = getFOModOrder((char *)stepOrder); + xmlFree(stepOrder); + + int stepCount = 0; + FOModStep_t * steps = parseInstallSteps(cur, &stepCount); + + fomod->steps = steps; + fomod->stepCount = stepCount; + } else if(xmlStrcmp(cur->name, (const xmlChar *) "conditionalFileInstalls") == 0) { + fomod->condFiles = NULL; + fomod->condFilesCount = 0; + + xmlNodePtr patterns = cur->children; + if(patterns != NULL) { + xmlNodePtr currentPattern = patterns->children; + + while(currentPattern != NULL) { + xmlNodePtr patternChild = currentPattern->children; + + while(patternChild != NULL) { + fomod->condFilesCount += 1; + fomod->condFiles = realloc(fomod->condFiles, fomod->condFilesCount * sizeof(FOModCondFile_t)); + FOModCondFile_t * condFile = &(fomod->condFiles[fomod->condFilesCount - 1]); + + condFile->fileCount = 0; + condFile->files = NULL; + condFile->flagCount = 0; + condFile->requiredFlags = NULL; + + if(xmlStrcmp(patternChild->name, (xmlChar *)"dependencies") == 0) { + xmlNodePtr flagNode = patternChild->children; + + while(flagNode != NULL) { + condFile->flagCount += 1; + condFile->requiredFlags = realloc(condFile->requiredFlags, condFile->flagCount * sizeof(FOModFlag_t)); + FOModFlag_t * flag = &(condFile->requiredFlags[condFile->flagCount - 1]); + flag->name = (char *) xmlGetProp(flagNode, (const xmlChar *) "flag"); + flag->value = (char *) xmlGetProp(flagNode, (const xmlChar *) "value"); + + flagNode = flagNode->next; + } + + + } else if(xmlStrcmp(patternChild->name, (xmlChar *)"files") == 0) { + xmlNodePtr filesNode = patternChild->children; + + while(filesNode != NULL) { + condFile->fileCount += 1; + condFile->files = realloc(condFile->files, condFile->fileCount * sizeof(FOModFile_t)); + FOModFile_t * flag = &(condFile->files[condFile->fileCount - 1]); + flag->source = (char *) xmlGetProp(filesNode, (const xmlChar *) "source"); + flag->destination = (char *) xmlGetProp(filesNode, (const xmlChar *) "destination"); + flag->priority = 0; + flag->isFolder = xmlStrcmp(patternChild->name, (xmlChar *) "folder") == 0; + + filesNode = filesNode->next; + } + } + patternChild = patternChild->next; + } + currentPattern = currentPattern->next; + } + } + } + cur = cur->next; + } + + xmlFreeDoc(doc); +} diff --git a/src/fomod.h b/src/fomod.h new file mode 100644 index 0000000..0651ff8 --- /dev/null +++ b/src/fomod.h @@ -0,0 +1,71 @@ +#ifndef __FOMOD_H__ +#define __FOMOD_H__ + +#include + +typedef enum FOModOrder { ASC, DESC, ORD } FOModOrder_t; +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 FOModFlag { + char * name; + char * value; +} FOModFlag_t; + +typedef struct FOModFile { + char * source; + char * destination; + int priority; + bool isFolder; +} FOModFile_t; + +typedef struct FOModPlugin { + char * description; + char * image; + FOModFlag_t * flags; + int flagCount; + FOModFile_t * files; + int fileCount; + TypeDescriptor_t type; + char * name; +} FOModPlugin_t; + +//combine group and plugins +typedef struct FOModGroup { + FOModPlugin_t * plugins; + int pluginCount; + GroupType_t type; + char * name; + FOModOrder_t order; +} FOModGroup_t; + +//combine installStep and optionalFileGroups +typedef struct FOModStep { + FOModOrder_t optionOrder; + FOModGroup_t * groups; + char * name; + FOModFlag_t * requiredFlags; + int flagCount; +} FOModStep_t; + +typedef struct FOModCondFile { + FOModFlag_t * requiredFlags; + unsigned int flagCount; + FOModFile_t * files; + unsigned int fileCount; +} FOModCondFile_t; + +typedef struct FOMod { + char * moduleName; + char * moduleImage; + char ** requiredInstallFiles; + FOModOrder_t stepOrder; + FOModStep_t * steps; + int stepCount; + FOModCondFile_t * condFiles; + int condFilesCount; +} FOMod_t; + +void freeFOMod(FOMod_t * fomod); + +#endif diff --git a/src/main.c b/src/main.c index 70f1c4d..c9a8066 100644 --- a/src/main.c +++ b/src/main.c @@ -392,7 +392,7 @@ int main(int argc, char ** argv) { //check to NOT run this as root //creating folder as root will causes lots of headache //such as problem with access rights and taking the risk of - //running system as root (wich is a big security issue) + //running system as root (which is a big security issue) if(getuid() == 0) { printf("For the first please run without sudo\n"); returnValue = EXIT_FAILURE;