split the cli and the core functionnalities
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
#include "archives.h"
|
||||
#include "file.h"
|
||||
|
||||
#include <sys/wait.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <glib.h>
|
||||
|
||||
int archive_unzip(char * path, char * outdir) {
|
||||
char * const args[] = {
|
||||
"unzip",
|
||||
"-LL", // to lowercase
|
||||
"-q",
|
||||
path,
|
||||
"-d",
|
||||
outdir,
|
||||
NULL
|
||||
};
|
||||
|
||||
pid_t pid = fork();
|
||||
|
||||
if(pid == 0) {
|
||||
execv("/usr/bin/unzip", args);
|
||||
return EXIT_FAILURE;
|
||||
} else {
|
||||
int returnValue;
|
||||
waitpid(pid, &returnValue, 0);
|
||||
|
||||
if(returnValue != 0) {
|
||||
fprintf(stderr, "\nFailed to decompress archive\n");
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
int archive_unrar(char * path, char * outdir) {
|
||||
char * const args[] = {
|
||||
"unrar",
|
||||
"x",
|
||||
"-y", //assume yes
|
||||
"-cl", // to lowercase
|
||||
path,
|
||||
outdir,
|
||||
NULL
|
||||
};
|
||||
|
||||
pid_t pid = fork();
|
||||
|
||||
if(pid == 0) {
|
||||
execv("/usr/bin/unrar", args);
|
||||
return EXIT_FAILURE;
|
||||
} else {
|
||||
int returnValue;
|
||||
waitpid(pid, &returnValue, 0);
|
||||
|
||||
if(returnValue != 0) {
|
||||
fprintf(stderr, "\nFailed to decompress archive\n");
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int archive_un7z(char * path, const char * outdir) {
|
||||
gchar * outParameter = g_strjoin("", "-o", outdir, NULL);
|
||||
|
||||
char * const args[] = {
|
||||
"7z",
|
||||
"-y", //assume yes
|
||||
"x",
|
||||
path,
|
||||
outParameter,
|
||||
NULL
|
||||
};
|
||||
|
||||
pid_t pid = fork();
|
||||
|
||||
if(pid == 0) {
|
||||
execv("/usr/bin/7z", args);
|
||||
return EXIT_FAILURE;
|
||||
} else {
|
||||
g_free(outParameter);
|
||||
int returnValue;
|
||||
waitpid(pid, &returnValue, 0);
|
||||
|
||||
if(returnValue != 0) {
|
||||
fprintf(stderr, "\nFailed to decompress archive\n");
|
||||
return returnValue;
|
||||
}
|
||||
//make everything lowercase since 7z don't have an argument for that.
|
||||
file_casefold(outdir);
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#ifndef __ARCHIVES_H__
|
||||
#define __ARCHIVES_H__
|
||||
|
||||
//all of these function will make every file into lowercase
|
||||
|
||||
/**
|
||||
* @brief Execute the unzip command
|
||||
* @param path path to archive
|
||||
* @param outdir output director
|
||||
* @return int return code
|
||||
*/
|
||||
int archive_unzip(char * path, char * outdir);
|
||||
|
||||
/**
|
||||
* @brief Execute the unrar command
|
||||
* @param path path to archive
|
||||
* @param outdir output director
|
||||
* @return int return code
|
||||
*/
|
||||
int archive_unrar(char * path, char * outdir);
|
||||
|
||||
/**
|
||||
* @brief Execute the 7z command
|
||||
* @param path path to archive
|
||||
* @param outdir output director
|
||||
* @return int return code
|
||||
*/
|
||||
int archive_un7z(char * path, const char * outdir);
|
||||
|
||||
#endif
|
||||
@@ -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;
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/types.h>
|
||||
#include <glib.h>
|
||||
#include "file.h"
|
||||
|
||||
static u_int32_t countSetBits(u_int32_t n) {
|
||||
// base case
|
||||
if (n == 0)
|
||||
return 0;
|
||||
else
|
||||
// if last bit set add 1 else add 0
|
||||
return (n & 1) + countSetBits(n >> 1);
|
||||
}
|
||||
|
||||
//TODO: add interruption support
|
||||
//simplest way to copy a file in c(linux)
|
||||
int file_copy(const char * path, const char * dest, u_int32_t flags) {
|
||||
int flagCount = countSetBits(flags);
|
||||
if(flagCount > 3) {
|
||||
fprintf(stderr, "Invalid flags for cp command\n");
|
||||
return -1;
|
||||
}
|
||||
//flags + cp + path + dest
|
||||
char * args[flagCount + 4];
|
||||
args[0] = "/bin/cp";
|
||||
args[1] = alloca((strlen(path) + 1) * sizeof(char));
|
||||
strcpy(args[1], path);
|
||||
args[2] = alloca((strlen(dest) + 1) * sizeof(char));
|
||||
strcpy(args[2], dest);
|
||||
int argIndex = 3;
|
||||
|
||||
if(flags & FILE_CP_LINK) {
|
||||
args[argIndex] = "--link";
|
||||
argIndex += 1;
|
||||
}
|
||||
if(flags & FILE_CP_RECURSIVE) {
|
||||
args[argIndex] = "-r";
|
||||
argIndex += 1;
|
||||
}
|
||||
if(flags & FILE_CP_NO_TARGET_DIR) {
|
||||
args[argIndex] = "-T";
|
||||
argIndex += 1;
|
||||
}
|
||||
|
||||
args[argIndex] = NULL;
|
||||
|
||||
int pid = fork();
|
||||
if(pid == 0) {
|
||||
//discard the const. since we are in a fork we don't care.
|
||||
execv("/bin/cp", args);
|
||||
return 0;
|
||||
} else {
|
||||
int returnValue;
|
||||
waitpid(pid, &returnValue, 0);
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
int file_delete(const char * path, bool recursive) {
|
||||
int pid = fork();
|
||||
if(pid == 0) {
|
||||
if(recursive) {
|
||||
execl("/bin/rm", "/bin/rm", "-r", path, NULL);
|
||||
} else {
|
||||
execl("/bin/rm", "/bin/rm", path, NULL);
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
int returnValue;
|
||||
waitpid(pid, &returnValue, 0);
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
int file_move(const char * source, const char * destination) {
|
||||
int pid = fork();
|
||||
if(pid == 0) {
|
||||
execl("/bin/mv", "/bin/mv", source, destination, NULL);
|
||||
return 0;
|
||||
} else {
|
||||
int returnValue;
|
||||
waitpid(pid, &returnValue, 0);
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//rename a folder and all subfolder and files to lowercase
|
||||
//TODO: error handling
|
||||
void file_casefold(const char * folder) {
|
||||
DIR * d = opendir(folder);
|
||||
struct dirent *dir;
|
||||
if (d) {
|
||||
while ((dir = readdir(d)) != NULL) {
|
||||
//removes .. & . from the list
|
||||
if(strcmp(dir->d_name, "..") != 0 && strcmp(dir->d_name, ".") != 0) {
|
||||
//only look at ascii and hope for the best.
|
||||
gchar * file = g_build_filename(folder, dir->d_name, NULL);
|
||||
|
||||
gchar * destinationName = g_ascii_strdown(dir->d_name, -1);
|
||||
gchar * destination = g_build_path("/", folder,destinationName, NULL);
|
||||
|
||||
|
||||
if(strcmp(destinationName, dir->d_name) != 0) {
|
||||
|
||||
int result = file_move(file, destination);
|
||||
if(result != EXIT_SUCCESS) {
|
||||
fprintf(stderr, "Move failed: %s => %s \n", dir->d_name, destinationName);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
g_free(file);
|
||||
g_free(destinationName);
|
||||
|
||||
if(dir->d_type == DT_DIR) {
|
||||
file_casefold(destination);
|
||||
}
|
||||
|
||||
g_free(destination);
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
}
|
||||
}
|
||||
|
||||
const char * file_extractLastPart(const char * filePath, const char delimeter) {
|
||||
const int length = strlen(filePath);
|
||||
long index = -1;
|
||||
for(long i= length - 1; i >= 0; i--) {
|
||||
if(filePath[i] == delimeter) {
|
||||
index = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(index <= 0 || index == length) return NULL;
|
||||
return &filePath[index];
|
||||
}
|
||||
|
||||
const char * file_extractExtension(const char * filePath) {
|
||||
return file_extractLastPart(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
@@ -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));
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
#include "group.h"
|
||||
#include "fomodTypes.h"
|
||||
#include "xmlUtil.h"
|
||||
#include "string.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
static 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;
|
||||
}
|
||||
}
|
||||
|
||||
static 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, "CouldBeUsable") == 0) {
|
||||
return MAYBE_USABLE;
|
||||
} else if(strcmp(descriptor, "NotUsable") == 0) {
|
||||
return NOT_USABLE;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void grp_freeGroup(fomod_Group_t * group){
|
||||
free(group->name);
|
||||
if(group->pluginCount == 0) return;
|
||||
for(int pluginId = 0; pluginId < group->pluginCount; pluginId++) {
|
||||
fomod_Plugin_t * plugin = &group->plugins[pluginId];
|
||||
if(plugin->fileCount > 0) {
|
||||
for(int i = 0; i < plugin->fileCount; i++) {
|
||||
free(plugin->files[i].destination);
|
||||
free(plugin->files[i].source);
|
||||
}
|
||||
free(plugin->files);
|
||||
}
|
||||
|
||||
if(plugin->flagCount > 0) {
|
||||
for(int i = 0; i < plugin->flagCount; i++) {
|
||||
free(plugin->flags[i].value);
|
||||
free(plugin->flags[i].name);
|
||||
}
|
||||
free(plugin->flags);
|
||||
}
|
||||
|
||||
if(plugin->description != NULL)free(plugin->description);
|
||||
if(plugin->image != NULL)free(plugin->image);
|
||||
if(plugin->name != NULL)free(plugin->name);
|
||||
}
|
||||
free(group->plugins);
|
||||
group->plugins = NULL;
|
||||
group->pluginCount = 0;
|
||||
}
|
||||
|
||||
static int parseConditionFlags(fomod_Plugin_t * plugin, xmlNodePtr nodeElement) {
|
||||
xmlNodePtr flagNode = nodeElement->children;
|
||||
while(flagNode != NULL) {
|
||||
if(!xml_validateNode(&flagNode, true, "flag", NULL)) {
|
||||
if(plugin->flagCount > 0) {
|
||||
free(plugin->flags);
|
||||
}
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if(flagNode == NULL)continue;
|
||||
|
||||
plugin->flagCount += 1;
|
||||
plugin->flags = realloc(plugin->flags, plugin->flagCount * sizeof(fomod_Flag_t));
|
||||
|
||||
fomod_Flag_t * flag = &plugin->flags[plugin->flagCount - 1];
|
||||
|
||||
flag->name = xml_freeAndDup(xmlGetProp(flagNode, (const xmlChar *) "name"));
|
||||
flag->value = xml_freeAndDup(xmlNodeGetContent(flagNode));
|
||||
|
||||
flagNode = flagNode->next;
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
static int parseGroupFiles(fomod_Plugin_t * plugin, xmlNodePtr nodeElement) {
|
||||
xmlNodePtr fileNode = nodeElement->children;
|
||||
while(fileNode != NULL) {
|
||||
if(!xml_validateNode(&fileNode, true, "folder", "file", NULL)) {
|
||||
fprintf(stderr, "Unexpected node in files");
|
||||
//TODO: free
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if(fileNode == NULL)continue;
|
||||
|
||||
plugin->fileCount += 1;
|
||||
|
||||
plugin->files = realloc(plugin->files, (plugin->fileCount + 1) * sizeof(fomod_File_t));
|
||||
fomod_File_t * file = &plugin->files[plugin->fileCount - 1];
|
||||
|
||||
file->destination = xml_freeAndDup(xmlGetProp(fileNode, (const xmlChar *) "destination"));
|
||||
file->source = xml_freeAndDup(xmlGetProp(fileNode, (const xmlChar *) "source"));
|
||||
|
||||
//TODO: test if it's a number
|
||||
xmlChar * priority = xmlGetProp(fileNode, (const xmlChar *) "priority");
|
||||
if(priority == NULL) {
|
||||
file->priority = 0;
|
||||
} else {
|
||||
file->priority = atoi((char *)priority);
|
||||
}
|
||||
xmlFree(priority);
|
||||
file->isFolder = xmlStrcmp(fileNode->name, (const xmlChar *) "folder") == 0;
|
||||
fileNode = fileNode->next;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
static int parseNodeElement(fomod_Plugin_t * plugin, xmlNodePtr nodeElement) {
|
||||
if(xmlStrcmp(nodeElement->name, (const xmlChar *) "description") == 0) {
|
||||
plugin->description = xml_freeAndDup(xmlNodeGetContent(nodeElement));
|
||||
} else if(xmlStrcmp(nodeElement->name, (const xmlChar *) "image") == 0) {
|
||||
plugin->image = xml_freeAndDup(xmlGetProp(nodeElement, (const xmlChar *) "path"));
|
||||
} else if(xmlStrcmp(nodeElement->name, (const xmlChar *) "conditionFlags") == 0) {
|
||||
return parseConditionFlags(plugin, nodeElement);
|
||||
} else if(xmlStrcmp(nodeElement->name, (const xmlChar *) "files") == 0) {
|
||||
return parseGroupFiles(plugin, nodeElement);
|
||||
} else if(xmlStrcmp(nodeElement->name, (const xmlChar *) "typeDescriptor") == 0) {
|
||||
xmlNodePtr typeNode = nodeElement->children;
|
||||
if(!xml_validateNode(&typeNode, true, "type", NULL)) {
|
||||
fprintf(stderr, "Unexpected node in typeDescriptor");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
xmlChar * name = xmlGetProp(typeNode, (const xmlChar *) "name");
|
||||
plugin->type = getDescriptor((char *) name);
|
||||
xmlFree(name);
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
int grp_parseGroup(xmlNodePtr groupNode, fomod_Group_t* group) {
|
||||
xmlNodePtr pluginsNode = groupNode->children;
|
||||
if(!xml_validateNode(&pluginsNode, true, "plugins", NULL)) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
||||
group->name = xml_freeAndDup(xmlGetProp( groupNode, (const xmlChar *) "name"));
|
||||
|
||||
xmlChar * type = xmlGetProp(groupNode, (const xmlChar *) "type");
|
||||
group->type = getGroupType((const char *)type);
|
||||
xmlFree(type);
|
||||
|
||||
char * order = (char *) xmlGetProp(pluginsNode, (const xmlChar *) "order");
|
||||
group->order = fomod_getOrder(order);
|
||||
xmlFree(order);
|
||||
|
||||
fomod_Plugin_t * plugins = NULL;
|
||||
int pluginCount = 0;
|
||||
|
||||
xmlNodePtr currentPlugin = pluginsNode->children;
|
||||
while(currentPlugin != NULL) {
|
||||
if(!xml_validateNode(¤tPlugin, true, "plugin", NULL)) {
|
||||
//TODO handle error;
|
||||
printf("%d\n", __LINE__);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if(currentPlugin == NULL)continue;
|
||||
|
||||
|
||||
pluginCount += 1;
|
||||
plugins = realloc(plugins, pluginCount * sizeof(fomod_Plugin_t));
|
||||
fomod_Plugin_t * plugin = &plugins[pluginCount - 1];
|
||||
//initialise everything to 0 and null pointers
|
||||
memset(plugin, 0, sizeof(fomod_Plugin_t));
|
||||
|
||||
plugin->name = xml_freeAndDup(xmlGetProp(currentPlugin, (const xmlChar *) "name"));
|
||||
|
||||
xmlNodePtr nodeElement = currentPlugin->children;
|
||||
while(nodeElement != NULL) {
|
||||
if(parseNodeElement(plugin, nodeElement) != EXIT_SUCCESS)
|
||||
goto failure;
|
||||
nodeElement = nodeElement->next;
|
||||
}
|
||||
|
||||
|
||||
currentPlugin = currentPlugin->next;
|
||||
}
|
||||
|
||||
group->plugins = plugins;
|
||||
group->pluginCount = pluginCount;
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
failure:
|
||||
//we need to free all of our allocations since we can't expect ou parent function to know what we allocated and what we haven't.
|
||||
group->plugins = plugins;
|
||||
group->pluginCount = pluginCount;
|
||||
grp_freeGroup(group);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,345 @@
|
||||
#include <fomod.h>
|
||||
#include "group.h"
|
||||
#include "libxml/tree.h"
|
||||
#include "xmlUtil.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static int parseVisibleNode(xmlNodePtr node, FOModStep_t * step) {
|
||||
xmlNodePtr requiredFlagsNode = node->children;
|
||||
|
||||
while (requiredFlagsNode != NULL) {
|
||||
|
||||
if(!xml_validateNode(&requiredFlagsNode, true, "flagDependency", NULL)) {
|
||||
//TODO: handle error
|
||||
printf("%d\n", __LINE__);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if(requiredFlagsNode == NULL)break;
|
||||
|
||||
step->flagCount += 1;
|
||||
step->requiredFlags = realloc(step->requiredFlags, step->flagCount * sizeof(fomod_Flag_t));
|
||||
fomod_Flag_t * flag = &(step->requiredFlags[step->flagCount - 1]);
|
||||
flag->name = xml_freeAndDup(xmlGetProp(requiredFlagsNode, (const xmlChar *) "flag"));
|
||||
flag->value = xml_freeAndDup(xmlGetProp(requiredFlagsNode, (const xmlChar *) "value"));
|
||||
|
||||
requiredFlagsNode = requiredFlagsNode->next;
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
static int parseOptionalFileGroup(xmlNodePtr node, FOModStep_t * step) {
|
||||
xmlChar * optionOrder = xmlGetProp(node, (const xmlChar *)"order");
|
||||
step->optionOrder = fomod_getOrder((char *)optionOrder);
|
||||
xmlFree(optionOrder);
|
||||
xmlNodePtr group = node->children;
|
||||
while(group != NULL) {
|
||||
if(!xml_validateNode(&group, true, "group", NULL)) {
|
||||
//TODO: handle error
|
||||
printf("%d\n", __LINE__);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if(group == NULL)break;
|
||||
|
||||
step->groupCount += 1;
|
||||
step->groups = realloc(step->groups, step->groupCount * sizeof(fomod_Group_t));
|
||||
int status = grp_parseGroup(group, &step->groups[step->groupCount - 1]);
|
||||
|
||||
if(status != EXIT_SUCCESS) {
|
||||
//TODO: handle error
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
group = group->next;
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
static FOModStep_t * parseInstallSteps(xmlNodePtr installStepsNode, int * stepCount) {
|
||||
FOModStep_t * steps = NULL;
|
||||
*stepCount = 0;
|
||||
|
||||
xmlNodePtr stepNode = installStepsNode->children;
|
||||
while(stepNode != NULL) {
|
||||
//skipping the text node
|
||||
if(!xml_validateNode(&stepNode, true, "installStep", NULL)) {
|
||||
//TODO: handle error
|
||||
printf("%d\n", __LINE__);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if(stepNode == NULL)break;
|
||||
|
||||
*stepCount += 1;
|
||||
steps = realloc(steps, *stepCount * sizeof(FOModStep_t));
|
||||
|
||||
FOModStep_t * step = &steps[*stepCount - 1];
|
||||
step->name = xml_freeAndDup(xmlGetProp(stepNode, (const xmlChar *)"name"));
|
||||
step->requiredFlags = NULL;
|
||||
step->flagCount = 0;
|
||||
step->groupCount = 0;
|
||||
step->groups = NULL;
|
||||
|
||||
xmlNodePtr stepchildren = stepNode->children;
|
||||
|
||||
while(stepchildren != NULL) {
|
||||
if(xmlStrcmp(stepchildren->name, (const xmlChar *) "visible") == 0) {
|
||||
parseVisibleNode(stepchildren, step);
|
||||
} else if(xmlStrcmp(stepchildren->name, (const xmlChar *) "optionalFileGroups") == 0) {
|
||||
parseOptionalFileGroup(stepchildren, step);
|
||||
}
|
||||
stepchildren = stepchildren->next;
|
||||
}
|
||||
|
||||
stepNode = stepNode->next;
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
|
||||
static int parseDependencies(xmlNodePtr node, fomod_CondFile_t * condFile) {
|
||||
xmlNodePtr flagNode = node->children;
|
||||
|
||||
if(!xml_validateNode(&flagNode, true, "flagDependency", NULL)) {
|
||||
//TODO: handle error
|
||||
printf("%d\n", __LINE__);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
while(flagNode != NULL) {
|
||||
condFile->flagCount += 1;
|
||||
condFile->requiredFlags = realloc(condFile->requiredFlags, condFile->flagCount * sizeof(fomod_Flag_t));
|
||||
fomod_Flag_t * flag = &(condFile->requiredFlags[condFile->flagCount - 1]);
|
||||
flag->name = xml_freeAndDup(xmlGetProp(flagNode, (const xmlChar *) "flag"));
|
||||
flag->value = xml_freeAndDup(xmlGetProp(flagNode, (const xmlChar *) "value"));
|
||||
|
||||
flagNode = flagNode->next;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
static int parseFiles(xmlNodePtr node, fomod_CondFile_t * condFile) {
|
||||
xmlNodePtr filesNode = node->children;
|
||||
|
||||
|
||||
while(filesNode != NULL) {
|
||||
if(!xml_validateNode(&filesNode, true, "folder", "file", NULL)) {
|
||||
//TODO: handle error
|
||||
printf("%d\n", __LINE__);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
condFile->fileCount += 1;
|
||||
condFile->files = realloc(condFile->files, condFile->fileCount * sizeof(fomod_File_t));
|
||||
fomod_File_t * flag = &(condFile->files[condFile->fileCount - 1]);
|
||||
flag->source = xml_freeAndDup(xmlGetProp(filesNode, (const xmlChar *) "source"));
|
||||
flag->destination = xml_freeAndDup(xmlGetProp(filesNode, (const xmlChar *) "destination"));
|
||||
flag->priority = 0;
|
||||
flag->isFolder = xmlStrcmp(filesNode->name, (xmlChar *) "folder") == 0;
|
||||
|
||||
filesNode = filesNode->next;
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
static int parseConditionalInstalls(xmlNodePtr node, FOMod_t * fomod) {
|
||||
xmlNodePtr patterns = node->children;
|
||||
if(patterns != NULL) {
|
||||
if(!xml_validateNode(&patterns, true, "patterns", NULL)) {
|
||||
//TODO: handle error
|
||||
printf("%d\n", __LINE__);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
xmlNodePtr currentPattern = patterns->children;
|
||||
while(currentPattern != NULL) {
|
||||
xmlNodePtr patternChild = currentPattern->children;
|
||||
|
||||
if(!xml_validateNode(&patternChild, true, "pattern", NULL)) {
|
||||
//TODO: handle error
|
||||
printf("%d\n", __LINE__);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
while(patternChild != NULL) {
|
||||
fomod->condFilesCount += 1;
|
||||
fomod->condFiles = realloc(fomod->condFiles, fomod->condFilesCount * sizeof(fomod_CondFile_t));
|
||||
fomod_CondFile_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) {
|
||||
if(parseDependencies(patternChild, condFile) != EXIT_SUCCESS) {
|
||||
//TODO: handle error
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
} else if(xmlStrcmp(patternChild->name, (xmlChar *)"files") == 0) {
|
||||
if(parseFiles(patternChild, condFile) != EXIT_SUCCESS) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
patternChild = patternChild->next;
|
||||
}
|
||||
currentPattern = currentPattern->next;
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
fomod->condFiles = NULL;
|
||||
fomod->condFilesCount = 0;
|
||||
fomod->requiredInstallFiles = NULL;
|
||||
fomod->stepCount = 0;
|
||||
fomod->stepOrder = 0;
|
||||
fomod->steps = NULL;
|
||||
fomod->moduleImage = NULL;
|
||||
fomod->moduleName = NULL;
|
||||
|
||||
doc = xmlParseFile(fomodFile);
|
||||
|
||||
if(doc == NULL) {
|
||||
fprintf(stderr, "Document is not a valid xml file\n");
|
||||
return ERR_FAILURE;
|
||||
}
|
||||
|
||||
|
||||
cur = xmlDocGetRootElement(doc);
|
||||
if(cur == NULL) {
|
||||
fprintf(stderr, "emptyDocument");
|
||||
xmlFreeDoc(doc);
|
||||
return ERR_FAILURE;
|
||||
}
|
||||
|
||||
if(xmlStrcmp(cur->name, (const xmlChar *) "config") != 0) {
|
||||
fprintf(stderr, "document of the wrong type, root node is '%s' instead of config\n", cur->name);
|
||||
xmlFreeDoc(doc);
|
||||
return ERR_FAILURE;
|
||||
}
|
||||
|
||||
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) {
|
||||
fomod->moduleImage = xml_freeAndDup(xmlGetProp(cur, (const xmlChar *)"path"));
|
||||
} else if(xmlStrcmp(cur->name, (const xmlChar *)"requiredInstallFiles") == 0) {
|
||||
//TODO: support non empty destination.
|
||||
xmlNodePtr requiredInstallFile = cur->children;
|
||||
while(requiredInstallFile != NULL) {
|
||||
if(xml_validateNode(&requiredInstallFile, true, "folder", "file", NULL)) {
|
||||
//TODO: handle error
|
||||
printf("%d\n", __LINE__);
|
||||
exit(ERR_FAILURE);
|
||||
}
|
||||
|
||||
int size = fomod_countUntilNull(fomod->requiredInstallFiles, sizeof(char **)) + 2;
|
||||
fomod->requiredInstallFiles = realloc(fomod->requiredInstallFiles, sizeof(char *) * size);
|
||||
//ensure it is null terminated
|
||||
fomod->requiredInstallFiles[size - 1] = NULL;
|
||||
fomod->requiredInstallFiles[size - 2] = xml_freeAndDup(xmlGetProp(requiredInstallFile, (const xmlChar *)"source"));
|
||||
|
||||
requiredInstallFile = cur->children;
|
||||
}
|
||||
} else if(xmlStrcmp(cur->name, (const xmlChar *)"installSteps") == 0) {
|
||||
if(fomod->steps != NULL) {
|
||||
fprintf(stderr, "Multiple 'installSteps' tags");
|
||||
//TODO: handle error
|
||||
return ERR_FAILURE;
|
||||
}
|
||||
|
||||
xmlChar * stepOrder = xmlGetProp(cur, (xmlChar *)"order");
|
||||
fomod->stepOrder = fomod_getOrder((char *)stepOrder);
|
||||
xmlFree(stepOrder);
|
||||
|
||||
int stepCount = 0;
|
||||
FOModStep_t * steps = parseInstallSteps(cur, &stepCount);
|
||||
|
||||
if(steps == NULL) {
|
||||
fprintf(stderr, "Failed to parse the install steps\n");
|
||||
|
||||
//TODO: manage the error properly
|
||||
return ERR_FAILURE;
|
||||
}
|
||||
|
||||
fomod->steps = steps;
|
||||
fomod->stepCount = stepCount;
|
||||
} else if(xmlStrcmp(cur->name, (const xmlChar *) "conditionalFileInstalls") == 0) {
|
||||
parseConditionalInstalls(cur, 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;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
#include "xmlUtil.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
char * xml_freeAndDup(xmlChar * line) {
|
||||
char * free = strdup((const char *) line);
|
||||
xmlFree(line);
|
||||
return free;
|
||||
}
|
||||
|
||||
fomod_Order_t fomod_getOrder(const char * order) {
|
||||
if(order == NULL || strcmp(order, "Ascending") == 0) {
|
||||
return ASC;
|
||||
} else if(strcmp(order, "Explicit") == 0) {
|
||||
return ORD;
|
||||
} else if(strcmp(order, "Descending") == 0) {
|
||||
return DESC;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
//replace \ in the path by /
|
||||
void xml_fixPath(char * path) {
|
||||
while(*path != '\0') {
|
||||
if(*path == '\\')*path = '/';
|
||||
path++;
|
||||
}
|
||||
}
|
||||
|
||||
int fomod_countUntilNull(void * pointers, size_t typeSize) {
|
||||
int i = 0;
|
||||
char * arithmetic = (char *)pointers;
|
||||
while(arithmetic != NULL) {
|
||||
arithmetic += typeSize;
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
//names cannot contain false
|
||||
//need to be null terminated
|
||||
bool xml_validateNode(xmlNodePtr * node, bool skipText, const char * names, ...) {
|
||||
va_list namesPtr;
|
||||
|
||||
while(*node != NULL && xmlStrcmp((*node)->name, (const xmlChar *)"text") == 0) {
|
||||
if(skipText) {
|
||||
(*node) = (*node)->next;
|
||||
} else {
|
||||
//could not skip and the node was a text node.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(*node == NULL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
va_start(namesPtr, names);
|
||||
|
||||
const char * validName = names;
|
||||
while(validName != NULL) {
|
||||
if(xmlStrcmp((*node)->name, (const xmlChar *)validName) == 0) {
|
||||
va_end(namesPtr);
|
||||
return true;
|
||||
}
|
||||
validName = va_arg(namesPtr, char *);
|
||||
}
|
||||
|
||||
va_end(namesPtr);
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef __XML_UTIL_H__
|
||||
#define __XML_UTIL_H__
|
||||
|
||||
#include <libxml/parser.h>
|
||||
#include <stdbool.h>
|
||||
#include <fomod.h>
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param node a pointer to the current node pointer (xmlNode **)
|
||||
* @param skipText if a text element is found skip it.
|
||||
* @param names variadic of the valid names.
|
||||
* @return return true if it found a valid node
|
||||
*/
|
||||
bool xml_validateNode(xmlNodePtr * node, bool skipText, const char * names, ...);
|
||||
|
||||
/**
|
||||
* @brief Free memory of and xmlChar and return a strdup version. just to make sure there is nothing remaining in libxml
|
||||
*/
|
||||
char * xml_freeAndDup(xmlChar * line);
|
||||
|
||||
|
||||
fomod_Order_t fomod_getOrder(const char * order);
|
||||
|
||||
/**
|
||||
* @brief replace / by \
|
||||
* @param path
|
||||
*/
|
||||
void xml_fixPath(char * path);
|
||||
|
||||
/**
|
||||
* @brief Count the number of step before null
|
||||
*
|
||||
* @param pointers pointer to the list
|
||||
* @param typeSize size of each element of the list
|
||||
* @return size
|
||||
*/
|
||||
int fomod_countUntilNull(void * pointers, size_t typeSize);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
#include <constants.h>
|
||||
#include <gameData.h>
|
||||
#include <steam.h>
|
||||
|
||||
#include <glib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <stdio.h>
|
||||
|
||||
error_t gameData_getDataPath(int appid, char ** destination) {
|
||||
GHashTable * gamePaths;
|
||||
error_t status = steam_searchGames(&gamePaths);
|
||||
if(status == ERR_FAILURE) {
|
||||
return ERR_FAILURE;
|
||||
}
|
||||
|
||||
int gameId = steam_gameIdFromAppId(appid);
|
||||
if(gameId < 0 ) {
|
||||
fprintf(stderr, "invalid appid");
|
||||
return ERR_FAILURE;
|
||||
}
|
||||
|
||||
const char * path = g_hash_table_lookup(gamePaths, &gameId);
|
||||
|
||||
if(path == NULL) {
|
||||
fprintf(stderr, "game not found\n");
|
||||
return ERR_FAILURE;
|
||||
}
|
||||
|
||||
char * gameFolder = g_build_filename(path, "steamapps/common", GAMES_NAMES[gameId], NULL);
|
||||
//the folder names for older an newer titles
|
||||
char * dataFolderOld = g_build_filename(gameFolder, "Data Files", NULL);
|
||||
char * dataFolderNew = g_build_filename(gameFolder, "Data", NULL);
|
||||
|
||||
g_free(gameFolder);
|
||||
|
||||
*destination = NULL;
|
||||
//struct stat sb;
|
||||
if(access(dataFolderOld, F_OK) == 0) {
|
||||
*destination = strdup(dataFolderOld);
|
||||
} else if(access(dataFolderNew, F_OK) == 0) {
|
||||
*destination = strdup(dataFolderNew);
|
||||
}
|
||||
|
||||
g_free(dataFolderNew);
|
||||
g_free(dataFolderOld);
|
||||
|
||||
if(*destination == NULL) return ERR_FAILURE;
|
||||
else return ERR_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#include <libaudit.h>
|
||||
#include <pwd.h>
|
||||
#include <string.h>
|
||||
#include "getHome.h"
|
||||
|
||||
char * getHome(void) {
|
||||
//not getting home from the env enable us to use sudo
|
||||
struct passwd *pw = getpwuid(audit_getloginuid());
|
||||
return strdup(pw->pw_dir);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <glib.h>
|
||||
|
||||
#include <install.h>
|
||||
|
||||
#include <constants.h>
|
||||
#include "archives.h"
|
||||
#include "file.h"
|
||||
|
||||
error_t install_addMod(char * filePath, int appId) {
|
||||
error_t resultError = ERR_SUCCESS;
|
||||
if (access(filePath, F_OK) != 0) {
|
||||
fprintf(stderr, "File not found\n");
|
||||
resultError = ERR_FAILURE;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
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;
|
||||
goto exit2;
|
||||
}
|
||||
|
||||
const char * filename = file_extractFileName(filePath);
|
||||
const char * extension = file_extractExtension(filename);
|
||||
char * lowercaseExtension = g_ascii_strdown(extension, -1);
|
||||
|
||||
char appIdStr[20];
|
||||
sprintf(appIdStr, "%d", appId);
|
||||
char * outdir = g_build_filename(configFolder, MOD_FOLDER_NAME, appIdStr, filename, NULL);
|
||||
|
||||
g_mkdir_with_parents(outdir, 0755);
|
||||
|
||||
int returnValue = EXIT_SUCCESS;
|
||||
printf("Adding mod, this process can be slow depending on your hardware\n");
|
||||
if(strcmp(lowercaseExtension, "rar") == 0) {
|
||||
returnValue = archive_unrar(filePath, outdir);
|
||||
} else if (strcmp(lowercaseExtension, "zip") == 0) {
|
||||
returnValue = archive_unzip(filePath, outdir);
|
||||
} else if (strcmp(lowercaseExtension, "7z") == 0) {
|
||||
returnValue = archive_un7z(filePath, outdir);
|
||||
} else {
|
||||
fprintf(stderr, "Unsupported format only zip/7z/rar are supported\n");
|
||||
returnValue = EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if(returnValue == EXIT_FAILURE)
|
||||
resultError = ERR_FAILURE;
|
||||
|
||||
printf("Done\n");
|
||||
|
||||
free(lowercaseExtension);
|
||||
free(outdir);
|
||||
exit2:
|
||||
free(configFolder);
|
||||
exit:
|
||||
return resultError;
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
#include <constants.h>
|
||||
|
||||
#include "loadOrder.h"
|
||||
#include "steam.h"
|
||||
#include "file.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "gameData.h"
|
||||
|
||||
//TODO: detect if the game is running
|
||||
//TODO: deploy the game
|
||||
|
||||
error_t order_listPlugins(int appid, GList ** plugins) {
|
||||
//save appid parsing
|
||||
//TODO: apply a similar mechanism everywhere
|
||||
size_t appidStrLen = snprintf(NULL, 0, "%d", appid) + 1;
|
||||
char appidStr[appidStrLen];
|
||||
sprintf(appidStr, "%d", appid);
|
||||
|
||||
char * dataFolder = NULL;
|
||||
error_t error = gameData_getDataPath(appid, &dataFolder);
|
||||
if(error != ERR_SUCCESS) {
|
||||
return ERR_FAILURE;
|
||||
}
|
||||
|
||||
//esp && esm files are loadable
|
||||
DIR * d = opendir(dataFolder);
|
||||
struct dirent *dir;
|
||||
if (d != NULL) {
|
||||
while ((dir = readdir(d)) != NULL) {
|
||||
const char * extension = file_extractExtension(dir->d_name);
|
||||
//folder don't have file extensions
|
||||
if(extension == NULL)
|
||||
continue;
|
||||
|
||||
if(strcmp(extension, "esp") == 0 || strcmp(extension, "esm") == 0) {
|
||||
*plugins = g_list_append(*plugins, strdup(dir->d_name));
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
}
|
||||
|
||||
free(dataFolder);
|
||||
return ERR_SUCCESS;
|
||||
}
|
||||
|
||||
static void removeCRLF_CR_LF(char * string) {
|
||||
int size = strlen(string);
|
||||
if(string[size - 1] == '\r') size--;
|
||||
if(string[size - 1] == '\n') size--;
|
||||
if(string[size - 1] == '\r') size--;
|
||||
string[size] = '\0';
|
||||
}
|
||||
|
||||
error_t order_getLoadOrder(int appid, GList ** order) {
|
||||
|
||||
GHashTable * gamePaths;
|
||||
error_t status = steam_searchGames(&gamePaths);
|
||||
if(status == ERR_FAILURE) {
|
||||
return ERR_FAILURE;
|
||||
}
|
||||
|
||||
GList * l_plugins = NULL;
|
||||
error_t error = order_listPlugins(appid, &l_plugins);
|
||||
if(error == ERR_FAILURE)
|
||||
return ERR_FAILURE;
|
||||
|
||||
|
||||
int gameId = steam_gameIdFromAppId(appid);
|
||||
if(gameId < 0 ) {
|
||||
return ERR_FAILURE;
|
||||
}
|
||||
|
||||
size_t appidStrLen = snprintf(NULL, 0, "%d", appid) + 1;
|
||||
char appidStr[appidStrLen];
|
||||
sprintf(appidStr, "%d", appid);
|
||||
|
||||
const char * path = g_hash_table_lookup(gamePaths, &gameId);
|
||||
|
||||
|
||||
//this is the path i would use in windows but it seems it is not avaliable in all wine versions.
|
||||
//char * loadOrderPath = g_build_filename(path, "steamapps/compatdata", appidStr, "pfx/drive_c/users/steamuser/AppData/Local/", GAMES_NAMES[gameId], "loadorder.txt", NULL);
|
||||
char * loadOrderPath = g_build_filename(path, "steamapps/compatdata", appidStr, "pfx/drive_c/users/steamuser/Local Settings/Application Data/", GAMES_NAMES[gameId], "loadorder.txt", NULL);
|
||||
|
||||
GList * l_currentLoadOrder = NULL;
|
||||
if(access(loadOrderPath, R_OK) == 0) {
|
||||
FILE * f_loadOrder = fopen(loadOrderPath, "r");
|
||||
|
||||
size_t length = 0;
|
||||
char * line = NULL;
|
||||
|
||||
while(getline(&line, &length, f_loadOrder) > 0) {
|
||||
if(line[0] == '#' || line[0] == '\n')
|
||||
continue;
|
||||
|
||||
removeCRLF_CR_LF(line);
|
||||
|
||||
l_currentLoadOrder = g_list_append(l_currentLoadOrder, strdup(line));
|
||||
}
|
||||
if(line != NULL)free(line);
|
||||
}
|
||||
|
||||
|
||||
GList * l_currentLoadOrderCursor = l_currentLoadOrder;
|
||||
GList * l_completeLoadOrder = NULL;
|
||||
|
||||
while(l_currentLoadOrderCursor != NULL) {
|
||||
char * modName = l_currentLoadOrderCursor->data;
|
||||
//TODO: finir sa
|
||||
GList * mod = g_list_find_custom(l_plugins, modName, (GCompareFunc)strcmp);
|
||||
if(mod == NULL) {
|
||||
//The plugin is no longer installed
|
||||
continue;
|
||||
} else {
|
||||
l_completeLoadOrder = g_list_append(l_completeLoadOrder, strdup(modName));
|
||||
}
|
||||
l_currentLoadOrderCursor = g_list_next(l_currentLoadOrderCursor);
|
||||
}
|
||||
|
||||
*order = l_completeLoadOrder;
|
||||
|
||||
g_list_free_full(l_plugins, free);
|
||||
g_list_free_full(l_currentLoadOrder, free);
|
||||
g_free(loadOrderPath);
|
||||
return ERR_SUCCESS;
|
||||
}
|
||||
|
||||
error_t order_setLoadOrder(int appid, GList * loadOrder) {
|
||||
GHashTable * gamePaths;
|
||||
error_t status = steam_searchGames(&gamePaths);
|
||||
if(status == ERR_FAILURE) {
|
||||
return ERR_FAILURE;
|
||||
}
|
||||
|
||||
int gameId = steam_gameIdFromAppId(appid);
|
||||
if(gameId < 0 ) {
|
||||
return ERR_FAILURE;
|
||||
}
|
||||
|
||||
size_t appidStrLen = snprintf(NULL, 0, "%d", appid) + 1;
|
||||
char appidStr[appidStrLen];
|
||||
sprintf(appidStr, "%d", appid);
|
||||
|
||||
const char * path = g_hash_table_lookup(gamePaths, &gameId);
|
||||
char * loadOrderPath = g_build_filename(path, "steamapps/compatdata", appidStr, "pfx/drive_c/users/steamuser/AppData/Local/", GAMES_NAMES[gameId], "loadorder.txt", NULL);
|
||||
|
||||
FILE * f_loadOrder = fopen(loadOrderPath, "w");
|
||||
while(loadOrder != NULL) {
|
||||
fwrite(loadOrder->data, sizeof(char), strlen(loadOrder->data), f_loadOrder);
|
||||
loadOrder = g_list_next(loadOrder);
|
||||
}
|
||||
fclose(f_loadOrder);
|
||||
|
||||
return ERR_SUCCESS;
|
||||
}
|
||||
|
||||
//TODO: support compression since it can change how we read the file
|
||||
//https://en.uesp.net/wiki/Skyrim_Mod:Mod_File_Format#Records
|
||||
//https://www.mwmythicmods.com/argent/tech/es_format.html
|
||||
error_t order_getModDependencies(const char * esmPath, GList ** dependencies) {
|
||||
FILE * file = fopen(esmPath, "r");
|
||||
|
||||
char sectionName[5];
|
||||
sectionName[4] = '\0';
|
||||
fread(sectionName, sizeof(char), 4, file);
|
||||
|
||||
size_t sizeFieldSize = 0;
|
||||
u_int8_t recordHeaderToIgnore = 0;
|
||||
|
||||
//the field "length" in the sub-record change between games.
|
||||
//TES4 => Fallout4 Oblivion Skyrim(+SE)
|
||||
//TES3 => Morrowind
|
||||
if(strcmp(sectionName, "TES3") == 0) {
|
||||
printf("Using tes3 file format\n");
|
||||
recordHeaderToIgnore = 8;
|
||||
sizeFieldSize = 4;
|
||||
} else if(strcmp(sectionName, "TES4") == 0) {
|
||||
printf("Using tes4 file format\n");
|
||||
recordHeaderToIgnore = 16;
|
||||
sizeFieldSize = 2;
|
||||
} else {
|
||||
fprintf(stderr, "Unrecognized file format %s\n", sectionName);
|
||||
fclose(file);
|
||||
return ERR_FAILURE;
|
||||
}
|
||||
|
||||
|
||||
u_int32_t lengthVal = 0;
|
||||
fread(&lengthVal, 4, 1, file);
|
||||
//ignore the rest of the data
|
||||
fseek(file, recordHeaderToIgnore, SEEK_CUR);
|
||||
|
||||
int64_t length = lengthVal;
|
||||
while(length > 0) {
|
||||
char sectionName[5];
|
||||
sectionName[4] = '\0';
|
||||
fread(sectionName, sizeof(char), 4, file);
|
||||
unsigned long subsectionLength = 0;
|
||||
fread(&subsectionLength, sizeFieldSize, 1, file);
|
||||
|
||||
length -= 8 + subsectionLength;
|
||||
if(strcmp(sectionName, "MAST") == 0) {
|
||||
char * dependency = malloc(subsectionLength + 1);
|
||||
dependency[subsectionLength] = '\0';
|
||||
fread(dependency, sizeof(char), subsectionLength, file);
|
||||
*dependencies = g_list_append(*dependencies, dependency);
|
||||
} else {
|
||||
fseek(file, subsectionLength, SEEK_CUR);
|
||||
}
|
||||
}
|
||||
fclose(file);
|
||||
return ERR_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#ifndef __MACRO_H__
|
||||
#define __MACRO_H__
|
||||
|
||||
//cannot be empty it's made for fixed size array like the ones in steam.h
|
||||
#define LEN(array) sizeof(array) / sizeof(array[0])
|
||||
|
||||
#endif
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "order.h"
|
||||
|
||||
//no function are declared in main it's just macros
|
||||
#include <constants.h>
|
||||
|
||||
#include "getHome.h"
|
||||
|
||||
typedef struct Mod {
|
||||
int modId;
|
||||
char * path;
|
||||
char * name;
|
||||
} Mod_t;
|
||||
|
||||
static gint compareOrder(const void * a, const void * b) {
|
||||
const Mod_t * ModA = a;
|
||||
const Mod_t * ModB = b;
|
||||
|
||||
if(ModA->modId == -1) return -1;
|
||||
if(ModB->modId == -1) return 1;
|
||||
|
||||
return ModA->modId - ModB->modId;
|
||||
}
|
||||
|
||||
GList * order_listMods(int appid) {
|
||||
char appidStr[10];
|
||||
sprintf(appidStr, "%d", appid);
|
||||
|
||||
char * home = getHome();
|
||||
char * modFolder = g_build_filename(home, MODLIB_WORKING_DIR, MOD_FOLDER_NAME, appidStr, NULL);
|
||||
free(home);
|
||||
|
||||
GList * list = NULL;
|
||||
DIR *d;
|
||||
struct dirent *dir;
|
||||
d = opendir(modFolder);
|
||||
if (d) {
|
||||
while ((dir = readdir(d)) != NULL) {
|
||||
//removes .. & . from the list
|
||||
if(strcmp(dir->d_name, "..") != 0 && strcmp(dir->d_name, ".") != 0) {
|
||||
int modId = -1;
|
||||
//TODO: remove order file
|
||||
char * modPath = g_build_filename(modFolder, dir->d_name, NULL);
|
||||
char * modOrder = g_build_filename(modPath, ORDER_FILE, NULL);
|
||||
FILE * fd_oder = fopen(modOrder, "r");
|
||||
g_free(modOrder);
|
||||
if(fd_oder != NULL) {
|
||||
fscanf(fd_oder, "%d", &modId);
|
||||
fclose(fd_oder);
|
||||
}
|
||||
|
||||
Mod_t * mod = alloca(sizeof(Mod_t));
|
||||
mod->modId = modId;
|
||||
//we are going to return this value so no alloca here
|
||||
mod->name = strdup(dir->d_name);
|
||||
//strdup but on the stack
|
||||
//add one for the \0
|
||||
mod->path = alloca((strlen(modPath) + 1) * sizeof(char));
|
||||
memcpy(mod->path, modPath, (strlen(modPath) + 1) * sizeof(char));
|
||||
|
||||
|
||||
list = g_list_append(list, mod);
|
||||
g_free(modPath);
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
}
|
||||
|
||||
list = g_list_sort(list, compareOrder);
|
||||
GList * orderedMods = NULL;
|
||||
|
||||
int index = 0;
|
||||
for(GList * p_list = list; p_list != NULL; p_list = g_list_next(p_list)) {
|
||||
Mod_t * mod = p_list->data;
|
||||
|
||||
gchar * modOrder = g_build_filename(mod->path, ORDER_FILE, NULL);
|
||||
|
||||
FILE * fd_oder = fopen(modOrder, "w+");
|
||||
g_free(modOrder);
|
||||
if(fd_oder != NULL) {
|
||||
fprintf(fd_oder, "%d", index);
|
||||
fclose(fd_oder);
|
||||
}
|
||||
|
||||
orderedMods = g_list_append(orderedMods, mod->name);
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
//do not use g_list_free since i used alloca everything is on the stack
|
||||
g_list_free(list);
|
||||
g_free(modFolder);
|
||||
return orderedMods;
|
||||
}
|
||||
|
||||
|
||||
error_t order_swapPlace(int appid, int modIdA, int modIdB) {
|
||||
char appidStr[10];
|
||||
sprintf(appidStr, "%d", appid);
|
||||
|
||||
char * home = getHome();
|
||||
char * modFolder = g_build_filename(home, MODLIB_WORKING_DIR, MOD_FOLDER_NAME, appidStr, NULL);
|
||||
free(home);
|
||||
|
||||
GList * list = order_listMods(appid);
|
||||
GList * listA = list;
|
||||
GList * listB = list;
|
||||
|
||||
for(int i = 0; i < modIdA; i++) {
|
||||
listA = g_list_next(listA);
|
||||
}
|
||||
|
||||
for(int i = 0; i < modIdB; i++) {
|
||||
listB = g_list_next(listB);
|
||||
}
|
||||
|
||||
if(listA == NULL || listB == NULL) {
|
||||
fprintf(stderr, "Invalid modId\n");
|
||||
return ERR_FAILURE;
|
||||
}
|
||||
|
||||
char * modAFolder = g_build_filename(modFolder, listA->data, ORDER_FILE, NULL);
|
||||
char * modBFolder = g_build_filename(modFolder, listB->data, ORDER_FILE, NULL);
|
||||
g_free(modFolder);
|
||||
|
||||
FILE * fileA = fopen(modAFolder, "w");
|
||||
FILE * fileB = fopen(modBFolder, "w");
|
||||
|
||||
g_free(modAFolder);
|
||||
g_free(modBFolder);
|
||||
|
||||
if(fileA == NULL || fileB == NULL) {
|
||||
fprintf(stderr, "Error could not open order file\n");
|
||||
return ERR_FAILURE;
|
||||
}
|
||||
|
||||
fprintf(fileA, "%d", modIdB);
|
||||
fprintf(fileB, "%d", modIdA);
|
||||
|
||||
fclose(fileA);
|
||||
fclose(fileB);
|
||||
|
||||
g_list_free_full(list, free);
|
||||
return ERR_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#include <sys/mount.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <stdlib.h>
|
||||
#include <glib.h>
|
||||
#include "overlayfs.h"
|
||||
#include <stdio.h>
|
||||
|
||||
overlay_errors_t overlay_mount(char ** sources, const char * dest, const char * upperdir, const char * workdir) {
|
||||
char * lowerdir = g_strjoinv(":", sources);
|
||||
char * mountData = g_strjoin("", "lowerdir=", lowerdir, ",workdir=", workdir, ",upperdir=", upperdir, NULL);
|
||||
|
||||
overlay_errors_t result = SUCESS;
|
||||
|
||||
if(access("/usr/bin/fuse-overlayfs", F_OK) == 0) {
|
||||
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", "-o", mountData, dest, NULL));
|
||||
} else {
|
||||
|
||||
int returnValue = 0;
|
||||
waitpid(pid, &returnValue, 0);
|
||||
if(returnValue != 0) {
|
||||
result = FAILURE;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result = NOT_INSTALLED;
|
||||
}
|
||||
free(lowerdir);
|
||||
free(mountData);
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#ifndef __OVERLAY_H__
|
||||
#define __OVERLAY_H__
|
||||
|
||||
typedef enum overlay_errors { SUCESS, NOT_INSTALLED, FAILURE } overlay_errors_t;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Overlayfs is what make it possible to deploy to the game files without altering anything.
|
||||
* it allows us to overlay multiple folder over the game files. like appling filters to an image.
|
||||
* @param sources a null ended table of folder that will overlay of the game files
|
||||
* @param dest the overlayed folder(the game data folder)
|
||||
* @param upperdir the director that will store the changed files
|
||||
* @param workdir a directory that will contains only temporary files.
|
||||
* @return int error code
|
||||
*/
|
||||
overlay_errors_t overlay_mount(char ** sources, const char * dest, const char * upperdir, const char * workdir);
|
||||
|
||||
#endif
|
||||
+289
@@ -0,0 +1,289 @@
|
||||
#include "steam.h"
|
||||
#include "macro.h"
|
||||
#include "getHome.h"
|
||||
|
||||
#include <constants.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <wordexp.h>
|
||||
|
||||
enum FieldIds { FIELD_PATH, FIELD_LABEL, FIELD_CONTENT_ID, FIELD_TOTAL_SIZE, FIELD_CLEAN_BYTES, FIELD_CORRUPTION, FIELD_APPS };
|
||||
|
||||
// relative to the home directory
|
||||
static const char * steamLibraries[] = {
|
||||
"/.steam/root/",
|
||||
"/.steam/steam/",
|
||||
"/.local/share/steam",
|
||||
//flatpack steam.
|
||||
"/.var/app/com.valvesoftware.Steam/.local/share/Steam"
|
||||
};
|
||||
|
||||
static int getFiledId(const char * field) {
|
||||
//replace with a hash + switch
|
||||
if(strcmp(field, "path") == 0) {
|
||||
return FIELD_PATH;
|
||||
} else if(strcmp(field, "label") == 0) {
|
||||
return FIELD_LABEL;
|
||||
} else if(strcmp(field, "contentid") == 0) {
|
||||
return FIELD_CONTENT_ID;
|
||||
} else if(strcmp(field, "totalsize") == 0) {
|
||||
return FIELD_TOTAL_SIZE;
|
||||
} else if(strcmp(field, "update_clean_bytes_tally") == 0) {
|
||||
return FIELD_CLEAN_BYTES;
|
||||
} else if(strcmp(field, "time_last_update_corruption") == 0) {
|
||||
return FIELD_CORRUPTION;
|
||||
} else if(strcmp(field, "apps") == 0) {
|
||||
return FIELD_APPS;
|
||||
} else {
|
||||
fprintf(stderr, "unknown field");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static steam_Libraries_t * parseVDF(const char * path, size_t * size, int * status) {
|
||||
FILE * fd = fopen(path, "r");
|
||||
char * line = NULL;
|
||||
size_t len = 0;
|
||||
|
||||
*status = EXIT_SUCCESS;
|
||||
|
||||
bool inQuotes = false;
|
||||
|
||||
steam_Libraries_t * libraries = NULL;
|
||||
*size = 0;
|
||||
|
||||
//skip the "libraryfolders" label & the first opening brace
|
||||
getline(&line, &len, fd);
|
||||
getline(&line, &len, fd);
|
||||
|
||||
int braceDepth = 0;
|
||||
|
||||
//can support up to 1024 car in quotes;
|
||||
char buffer[1024];
|
||||
bool isAppId = TRUE;
|
||||
int bufferIndex = 0;
|
||||
int nextFieldToFill = -1;
|
||||
|
||||
while (getline(&line, &len, fd) != -1) {
|
||||
for(int i = 0; line[i] != '\0'; i++) {
|
||||
switch (line[i]) {
|
||||
case '"':
|
||||
if(braceDepth == 0) {
|
||||
//this is a library id so we don't care.
|
||||
bufferIndex = 0;
|
||||
} else {
|
||||
//actual data
|
||||
if(inQuotes) {
|
||||
inQuotes = FALSE;
|
||||
buffer[bufferIndex] = '\0';
|
||||
if(nextFieldToFill == -1) {
|
||||
nextFieldToFill = getFiledId(buffer);
|
||||
if(nextFieldToFill == -1) {
|
||||
if(libraries != NULL) free(libraries);
|
||||
libraries = NULL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
} else {
|
||||
char * value = strndup(buffer, bufferIndex);
|
||||
steam_Libraries_t * library = &libraries[*size - 1];
|
||||
switch (nextFieldToFill) {
|
||||
case FIELD_PATH:
|
||||
library->path = value;
|
||||
break;
|
||||
|
||||
case FIELD_LABEL:
|
||||
library->label = value;
|
||||
break;
|
||||
|
||||
case FIELD_CONTENT_ID:
|
||||
library->contentId = value;
|
||||
break;
|
||||
|
||||
case FIELD_TOTAL_SIZE:
|
||||
library->totalSize = strtoul(value, NULL, 0);
|
||||
free(value);
|
||||
break;
|
||||
|
||||
case FIELD_CLEAN_BYTES:
|
||||
library->update_clean_bytes_tally = value;
|
||||
break;
|
||||
|
||||
case FIELD_CORRUPTION:
|
||||
library->time_last_update_corruption = value;
|
||||
break;
|
||||
|
||||
case FIELD_APPS:
|
||||
if(isAppId) {
|
||||
library->appsCount++;
|
||||
library->apps = realloc(library->apps, library->appsCount * sizeof(steam_App_t));
|
||||
unsigned int appid = strtoul(value, NULL, 10);
|
||||
library->apps[library->appsCount - 1].appid = appid;
|
||||
} else {
|
||||
library->apps[library->appsCount - 1].update = strtoul(value, NULL, 10);
|
||||
}
|
||||
free(value);
|
||||
isAppId = !isAppId;
|
||||
break;
|
||||
default:
|
||||
free(value);
|
||||
}
|
||||
//field apps is using braces so the syntax is different
|
||||
if(nextFieldToFill != FIELD_APPS)nextFieldToFill = -1;
|
||||
}
|
||||
} else {
|
||||
inQuotes = TRUE;
|
||||
}
|
||||
bufferIndex = 0;
|
||||
|
||||
}
|
||||
break;
|
||||
case '{':
|
||||
braceDepth++;
|
||||
if(braceDepth == 1) {
|
||||
*size += 1;
|
||||
libraries = realloc(libraries, sizeof(steam_Libraries_t) * (*size));
|
||||
memset(&libraries[*size - 1], 0, sizeof(steam_Libraries_t));
|
||||
}
|
||||
break;
|
||||
case '}':
|
||||
if(inQuotes) {
|
||||
fprintf(stderr, "Syntax error in VDF\n");
|
||||
//TODO: fix this leak
|
||||
free(libraries);
|
||||
libraries = NULL;
|
||||
*status = EXIT_FAILURE;
|
||||
goto exit;
|
||||
}
|
||||
if(nextFieldToFill == FIELD_APPS) {
|
||||
nextFieldToFill = -1;
|
||||
}
|
||||
braceDepth--;
|
||||
break;
|
||||
|
||||
default:
|
||||
if(inQuotes) {
|
||||
buffer[bufferIndex++] = line[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
if(line != NULL) free(line);
|
||||
fclose(fd);
|
||||
return libraries;
|
||||
}
|
||||
|
||||
static void freeLibraries(steam_Libraries_t * libraries, int size) {
|
||||
for(int i = 0; i < size; i++) {
|
||||
free(libraries[i].path);
|
||||
free(libraries[i].label);
|
||||
free(libraries[i].contentId);
|
||||
free(libraries[i].update_clean_bytes_tally);
|
||||
free(libraries[i].time_last_update_corruption);
|
||||
free(libraries[i].apps);
|
||||
}
|
||||
free(libraries);
|
||||
}
|
||||
|
||||
static GHashTable* gameTableSingleton = NULL;
|
||||
|
||||
void steam_freeGameTable() {
|
||||
if(gameTableSingleton != NULL)g_hash_table_destroy(gameTableSingleton);
|
||||
}
|
||||
|
||||
error_t steam_searchGames(GHashTable ** p_hashTable) {
|
||||
if(gameTableSingleton != NULL) {
|
||||
*p_hashTable = gameTableSingleton;
|
||||
return ERR_SUCCESS;
|
||||
}
|
||||
|
||||
steam_Libraries_t * libraries = NULL;
|
||||
size_t size = 0;
|
||||
char * home = getHome();
|
||||
|
||||
for(unsigned long i = 0; i < LEN(steamLibraries); i++) {
|
||||
char * path = g_build_filename(home, steamLibraries[i], "steamapps/libraryfolders.vdf", NULL);
|
||||
if (access(path, F_OK) == 0) {
|
||||
int parserStatus;
|
||||
libraries = parseVDF(path, &size, &parserStatus);
|
||||
if(parserStatus == EXIT_SUCCESS) {
|
||||
g_free(path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
g_free(path);
|
||||
}
|
||||
|
||||
free(home);
|
||||
if(libraries == NULL) {
|
||||
return ERR_FAILURE;
|
||||
}
|
||||
|
||||
GHashTable* table = g_hash_table_new_full(g_int_hash, g_int_equal, free, free);
|
||||
|
||||
//fill the table
|
||||
for(unsigned long i = 0; i < size; i++) {
|
||||
for(unsigned long j = 0; j < libraries[i].appsCount; j++) {
|
||||
int gameId = steam_gameIdFromAppId(libraries[i].apps[j].appid);
|
||||
if(gameId >= 0) {
|
||||
int * key = malloc(sizeof(int));
|
||||
*key = gameId;
|
||||
g_hash_table_insert(table, key, strdup(libraries[i].path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freeLibraries(libraries, size);
|
||||
*p_hashTable = table;
|
||||
gameTableSingleton = table;
|
||||
return ERR_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int steam_gameIdFromAppId(u_int32_t appid) {
|
||||
for(unsigned long k = 0; k < LEN(GAMES_APPIDS); k++) {
|
||||
if(appid == GAMES_APPIDS[k]) {
|
||||
return k;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user