aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--LICENSE22
-rw-r--r--Makefile127
-rw-r--r--README.md10
-rw-r--r--src/common.c21
-rw-r--r--src/common.h28
-rw-r--r--src/dxvk.c98
-rw-r--r--src/dxvk.h9
-rw-r--r--src/main.c47
-rw-r--r--src/main.h7
-rw-r--r--src/net.c100
-rw-r--r--src/net.h10
-rw-r--r--src/wine.c97
-rw-r--r--src/wine.h9
14 files changed, 588 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1037633
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+bin
+obj
+*.tar*
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d44d77e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+MIT License
+
+Copyright (c) 2020 Jan
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..bae52c5
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,127 @@
+# GENERAL VARIABLES
+NAME := polecat
+VERSION := 0.1.0
+TARGET ?= debug
+DEBUG := 0
+ifeq ($(TARGET),debug)
+ DEBUG := 1
+endif
+
+PRETTY_OUTPUT ?= 1
+STATIC ?= 0
+
+# CROSS COMPILATION SETUP
+CROSS ?=
+
+# COMMAND VARIABLES
+RM := rm -rf
+MKDIR := mkdir -p
+DOXYGEN := doxygen
+PKGCONFIG := $(CROSS)pkg-config
+CURLCONFIG := $(CROSS)curl-config
+
+
+ifndef PLATFORM
+ PLATFORM := $(HOSTPLATFORM)
+endif
+
+# CROSS COMPILATION ADAPTION
+
+# DIRECTORIES
+BIN_DIR := bin
+OBJ_DIR := obj
+
+SRC_DIR := src
+
+FILES := $(filter-out $(BIN_DIR) $(OBJ_DIR), $(wildcard *))
+
+# FLAGS
+COMMONFLAGS :=
+ifneq ($(STATIC), 0)
+ COMMONFLAGS += -static
+endif
+ifeq ($(DEBUG),0)
+ COMMONFLAGS += -O2
+else
+ COMMONFLAGS += -g
+endif
+CFLAGS := $(COMMONFLAGS) -Wall `$(PKGCONFIG) json-c --cflags` `$(CURLCONFIG) --cflags`
+LDFLAGS := `$(PKGCONFIG) json-c --libs` `$(CURLCONFIG) --libs`
+DEFINES := -DNAME=\"$(NAME)\" -DVERSION=\"$(VERSION)\"
+ifeq ($(DEBUG),1)
+ DEFINES += -DDEBUG
+endif
+
+# SOURCE CODE AND OBJECT FILES
+CC_SRC_FILES := $(wildcard $(SRC_DIR)/*.c)
+
+OBJ_FILES := $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/$(TARGET)/%.o, $(CC_SRC_FILES))
+
+
+# TARGETS
+default: $(BIN_DIR)/$(TARGET)/$(NAME)$(OUT_EXT)
+
+all: $(BIN_DIR)/$(TARGET)/$(NAME)$(OUT_EXT) docs test
+
+$(BIN_DIR)/$(TARGET):
+ ${MKDIR} $@
+
+$(OBJ_DIR)/$(TARGET):
+ ${MKDIR} $@
+
+$(BIN_DIR)/$(TARGET)/$(NAME)$(OUT_EXT): $(OBJ_FILES) | $(BIN_DIR)/$(TARGET)
+ ${LINK_STATUS}
+ ${RECIPE_IF} ${CROSS}${CC} -o$@ $^ ${CFLAGS} ${DEFINES} ${LDFLAGS} ${RECIPE_RESULT_LINK}
+
+$(OBJ_DIR)/$(TARGET)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)/$(TARGET)
+ ${COMPILE_STATUS}
+ ${RECIPE_IF} ${CROSS}${CC} -c -o$@ $< ${CFLAGS} ${DEFINES} ${RECIPE_RESULT_COMPILE}
+
+clean:
+ ${RM} ${BIN_DIR} ${OBJ_DIR} ${NAME}.tar 2> /dev/null
+
+loc:
+ -find ${SRC_DIR} ${INC_DIR} -name '*.cpp' -o -name '*.c' -o -name '*.h' -o -name '*.hpp' -type f | xargs wc -l
+
+tar: ${NAME}.tar
+
+dist: tar
+
+${NAME}.tar:
+ ${ARCHIVE_STATUS}
+ $(RECIPE_IF) tar -cf $@ ${FILES} $(RECIPE_RESULT_ARCHIVE)
+
+.PHONY: default all clean docs loc tar dist
+
+
+ifeq ($(PRETTY_OUTPUT),1)
+.SILENT:
+RECIPE_IF = if
+COMPILE_STATUS = printf "\033[K\033[0mBuilding object \033[1m$@\033[0m...\033[0m\r"
+COMPILE_OK = printf "\033[K\033[0;32mBuilt object \033[1;32m$@\033[0;32m \033[0m\n"
+COMPILE_FAILED = printf "\033[K\033[0;31mFailed building \033[1;31m$@\033[0;31m from\033[0m \033[1;31m$<\033[0;31m!\033[0m\n"; exit 1
+RECIPE_RESULT_COMPILE = ; then $(COMPILE_OK); else $(COMPILE_FAILED); fi
+ARCHIVE_STATUS = printf "\033[K\033[0mCreating library archive \033[1m$@\033[0m...\033[0m\r"
+ARCHIVE_OK = printf "\033[K\033[0;32mCreated library archive \033[1;32m$@\033[0;32m \033[0m\n"
+ARCHIVE_FAILED = printf "\033[K\033[0;31mFailed creating library archive \033[1;31m$@\033[0;31m from\033[0m \033[1;31m$<\033[0;31m!\033[0m\n"; exit 1
+RECIPE_RESULT_ARCHIVE = ; then $(ARCHIVE_OK); else $(ARCHIVE_FAILED); fi
+LINK_STATUS = printf "\033[K\033[0;0mLinking \033[1m$@\033[0;0m...\033[0m\r"
+LINK_OK = printf "\033[K\033[0;32mLinked \033[1;32m$@\033[0;32m \033[0m\n"
+LINK_FAILED = printf "\033[K\033[0;31mFailed linking \033[1;31m$@\033[0;31m!\nIf the build options, environment, or system packages have changed, run \'\033[1;31mmake clean\033[0;31m\' and try again.\033[0m\n"; exit 1
+RECIPE_RESULT_LINK = ; then $(LINK_OK); else $(LINK_FAILED); fi
+else
+RECIPE_IF =
+BUILD_STARTED =
+COMPILE_STATUS =
+COMPILE_OK = true
+COMPILE_FAILED = false; exit 1
+RECIPE_RESULT_COMPILE =
+ARCHIVE_STATUS =
+ARCHIVE_OK = true
+ARCHIVE_FAILED = false; exit 1
+RECIPE_RESULT_ARCHIVE =
+LINK_STATUS =
+LINK_OK = true
+LINK_FAILED = false; exit 1
+RECIPE_RESULT_LINK =
+endif
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a62e8bd
--- /dev/null
+++ b/README.md
@@ -0,0 +1,10 @@
+# polecat
+
+*What is polecat?*
+polecat is like lutris except it does none of the work for you and only downloads wine and dxvk
+
+*why?*
+I got sick of Lutris breaking every 2 weeks and know how to manage my own wine prefixes
+
+### License
+MIT \ No newline at end of file
diff --git a/src/common.c b/src/common.c
new file mode 100644
index 0000000..f3131e0
--- /dev/null
+++ b/src/common.c
@@ -0,0 +1,21 @@
+#include <stdio.h>
+#include <string.h>
+
+#include "common.h"
+
+void print_help(const struct Command* commands, const size_t size)
+{
+ int longestCommand = 0;
+
+ for (size_t i = 0; i < size; ++i)
+ {
+ int commandLength = strlen(commands[i].name);
+
+ if (commandLength > longestCommand) longestCommand = commandLength;
+ }
+
+ for (size_t i = 0; i < size; ++i)
+ {
+ printf("\t%-*s\t%s\n", longestCommand, commands[i].name, commands[i].description);
+ }
+}
diff --git a/src/common.h b/src/common.h
new file mode 100644
index 0000000..8b9d95b
--- /dev/null
+++ b/src/common.h
@@ -0,0 +1,28 @@
+#ifndef COMMON_H
+#define COMMON_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define ARRAY_LEN(arr) sizeof(arr) / sizeof(arr[0])
+
+#define WINE_API "https://lutris.net/api/runners/wine"
+#define DXVK_API "https://api.github.com/repos/lutris/dxvk/releases"
+
+#define USER_AGENT NAME "/" VERSION
+
+
+struct MemoryStruct {
+ uint8_t* memory;
+ size_t size;
+};
+
+struct Command {
+ char* name;
+ int (*func)(int, char**);
+ char* description;
+};
+
+void print_help(const struct Command*, size_t);
+
+#endif \ No newline at end of file
diff --git a/src/dxvk.c b/src/dxvk.c
new file mode 100644
index 0000000..32d7d69
--- /dev/null
+++ b/src/dxvk.c
@@ -0,0 +1,98 @@
+#include <stdio.h>
+#include <string.h>
+#include <json.h>
+#include <libgen.h>
+
+#include "dxvk.h"
+#include "net.h"
+#include "common.h"
+
+const static struct Command dxvk_commands[] = {
+ { .name = "install", .func = dxvk_install, .description = "download and install a dxvk version" },
+ { .name = "list", .func = dxvk_list, .description = "list available dxvk versions" },
+ { .name = "help", .func = dxvk_help, .description = "shows this message" },
+};
+
+int dxvk(int argc, char** argv)
+{
+ if (argc > 2)
+ {
+ for (int i = 0; i < ARRAY_LEN(dxvk_commands); ++i)
+ {
+ if (!strcmp(dxvk_commands[i].name, argv[2])) return dxvk_commands[i].func(argc, argv);
+ }
+ }
+
+ return dxvk_help(argc, argv);
+}
+
+
+int dxvk_install(int argc, char** argv)
+{
+ if (argc == 4)
+ {
+ struct json_object* runner = fetchJSON(DXVK_API);
+
+ if (runner)
+ {
+
+ int choice = atoi(argv[3]);
+
+ if (choice > json_object_array_length(runner) - 1 || choice < 0)
+ {
+ printf("`%i' is not a valid ID\n\nrun `polecat dxvk list' to get a valid ID", choice);
+ }
+ else
+ {
+ struct json_object* version = json_object_array_get_idx(runner, choice);
+ struct json_object* assets;
+
+ json_object_object_get_ex(version, "assets", &assets);
+ version =json_object_array_get_idx(assets, 0);
+
+ json_object_object_get_ex(version, "browser_download_url", &assets);
+
+ char* name = basename((char*)json_object_get_string(assets));
+
+ printf("Downloading %s", json_object_get_string(assets));
+ downloadFile(json_object_get_string(assets), name);
+ printf("\nDone\n");
+ }
+ }
+ }
+ else
+ {
+ puts("Usage: polecat dxvk download <ID>\n\nIDs are obtained via `polecat dxvk list' ");
+ }
+ return 0;
+}
+
+int dxvk_list(int argc, char** argv)
+{
+ struct json_object* runner = fetchJSON(DXVK_API);
+
+ if (runner)
+ {
+ puts("Installable DXVK versions:");
+
+ for (size_t i = 0; i < json_object_array_length(runner); ++i)
+ {
+ struct json_object* version = json_object_array_get_idx(runner, i);
+ struct json_object* name;
+
+ json_object_object_get_ex(version, "name", &name);
+ printf(" [%zu]\t%s\n", i, json_object_get_string(name));
+ }
+ }
+
+ return 0;
+}
+
+int dxvk_help(int argc, char** argv)
+{
+ puts("usage: polecat dxvk <command>\n\nList of commands:");
+
+ print_help(dxvk_commands, ARRAY_LEN(dxvk_commands));
+
+ return 0;
+} \ No newline at end of file
diff --git a/src/dxvk.h b/src/dxvk.h
new file mode 100644
index 0000000..ef423e2
--- /dev/null
+++ b/src/dxvk.h
@@ -0,0 +1,9 @@
+#ifndef DXVK_H
+#define DXVK_H
+
+int dxvk(int, char**);
+int dxvk_install(int, char**);
+int dxvk_list(int, char**);
+int dxvk_help(int, char**);
+
+#endif \ No newline at end of file
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..2e7655d
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,47 @@
+#include <stdio.h>
+#include <string.h>
+
+#include "main.h"
+#include "wine.h"
+#include "dxvk.h"
+#include "common.h"
+
+const static struct Command main_commands[] = {
+ { .name = "wine", .func = wine, .description = "manage wine versions" },
+ { .name = "dxvk", .func = dxvk, .description = "manage dxvk versions" },
+ { .name = "info", .func = main_info, .description = "show some information about polecat" },
+ { .name = "help", .func = main_help, .description = "displays this message" },
+};
+
+
+int main(int argc, char** argv)
+{
+ if (argc > 1)
+ {
+ for (int i = 0; i < ARRAY_LEN(main_commands); ++i)
+ {
+ if (!strcmp(main_commands[i].name, argv[1])) return main_commands[i].func(argc, argv);
+ }
+ }
+
+ return main_help(argc, argv);
+}
+
+int main_info(int argc, char** argv)
+{
+ printf("Version:\t\t%s\n"
+ "User-Agent:\t\t%s/%s\n",
+ VERSION,
+ NAME, VERSION);
+
+ return 0;
+}
+
+int main_help(int argc, char** argv)
+{
+ puts("usage: polecat <command>\n\nList of commands:");
+
+ print_help(main_commands, ARRAY_LEN(main_commands));
+
+ return 0;
+} \ No newline at end of file
diff --git a/src/main.h b/src/main.h
new file mode 100644
index 0000000..594b29e
--- /dev/null
+++ b/src/main.h
@@ -0,0 +1,7 @@
+#ifndef MAIN_H
+#define MAIN_H
+
+extern int main_help(int, char**);
+extern int main_info(int, char**);
+
+#endif \ No newline at end of file
diff --git a/src/net.c b/src/net.c
new file mode 100644
index 0000000..f7dc481
--- /dev/null
+++ b/src/net.c
@@ -0,0 +1,100 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <curl/curl.h>
+#include <json.h>
+
+#include "net.h"
+#include "common.h"
+
+size_t memoryCallback(void* contents, size_t size, size_t nmemb, void* userp)
+{
+ size_t realsize = size * nmemb;
+ struct MemoryStruct* mem = (struct MemoryStruct*)userp;
+
+ uint8_t* ptr = realloc(mem->memory, mem->size + realsize + 1);
+ if(ptr == NULL) {
+ /* out of memory! */
+ puts("out of memory");
+ return 0;
+ }
+
+ mem->memory = ptr;
+ memcpy(&(mem->memory[mem->size]), contents, realsize);
+ mem->size += realsize;
+ mem->memory[mem->size] = 0;
+
+ return realsize;
+}
+
+struct MemoryStruct* downloadToRam(const char* URL)
+{
+ CURL* curl_handle;
+ CURLcode res;
+
+ struct MemoryStruct* chunk = malloc(sizeof(struct MemoryStruct));
+
+ chunk->memory = malloc(1);
+ chunk->size = 0;
+
+ curl_global_init(CURL_GLOBAL_ALL);
+
+ curl_handle = curl_easy_init();
+
+ curl_easy_setopt(curl_handle, CURLOPT_URL, URL);
+ curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, memoryCallback);
+ curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void*)chunk);
+ curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, USER_AGENT);
+ curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1L);
+
+ res = curl_easy_perform(curl_handle);
+
+ long http_code = 0;
+ curl_easy_getinfo (curl_handle, CURLINFO_RESPONSE_CODE, &http_code);
+
+ if(res != CURLE_OK) {
+ fprintf(stderr, "curl_easy_perform() failed: %s\n",
+ curl_easy_strerror(res));
+ return NULL;
+ }
+ else if (http_code != 200)
+ {
+ fprintf(stderr, "Server didn't respond as expected [HTTP Error %li]\n", http_code);
+ return NULL;
+ }
+
+ curl_easy_cleanup(curl_handle);
+ curl_global_cleanup();
+
+ return chunk;
+}
+
+int downloadFile(const char* URL, const char* path)
+{
+ struct MemoryStruct* chunk = downloadToRam(URL);
+
+ if (chunk)
+ {
+ FILE* file = fopen(path, "wb");
+ fwrite(chunk->memory, chunk->size, 1, file);
+ fclose(file);
+
+ free(chunk->memory);
+ free(chunk);
+ }
+
+ return 0;
+}
+
+struct json_object* fetchJSON(const char* URL)
+{
+ struct MemoryStruct* chunk = downloadToRam(URL);
+
+ struct json_object* json = json_tokener_parse((char*)chunk->memory);
+
+ free(chunk->memory);
+ free(chunk);
+
+ return json;
+} \ No newline at end of file
diff --git a/src/net.h b/src/net.h
new file mode 100644
index 0000000..2b203fa
--- /dev/null
+++ b/src/net.h
@@ -0,0 +1,10 @@
+#ifndef NET_H
+#define NET_H
+
+#include <json.h>
+
+size_t WriteMemoryCallback(void*, size_t, size_t, void*);
+int downloadFile(const char*, const char*);
+struct json_object* fetchJSON(const char*);
+
+#endif \ No newline at end of file
diff --git a/src/wine.c b/src/wine.c
new file mode 100644
index 0000000..2c304ed
--- /dev/null
+++ b/src/wine.c
@@ -0,0 +1,97 @@
+#include <stdio.h>
+#include <string.h>
+#include <json.h>
+#include <libgen.h>
+
+#include "wine.h"
+#include "net.h"
+#include "common.h"
+
+const static struct Command wine_commands[] = {
+ { .name = "install", .func = wine_install, .description = "download and install a wine version from lutris" },
+ { .name = "list", .func = wine_list, .description = "list available wine versions" },
+ { .name = "help", .func = wine_help, .description = "shows this message" },
+};
+
+int wine(int argc, char** argv)
+{
+ if (argc > 2)
+ {
+ for (int i = 0; i < ARRAY_LEN(wine_commands); ++i)
+ {
+ if (!strcmp(wine_commands[i].name, argv[2])) return wine_commands[i].func(argc, argv);
+ }
+ }
+
+ return wine_help(argc, argv);
+}
+
+
+int wine_install(int argc, char** argv)
+{
+ if (argc == 4)
+ {
+ struct json_object* runner = fetchJSON(WINE_API);
+
+ if (runner)
+ {
+ struct json_object* versions;
+ json_object_object_get_ex(runner, "versions", &versions);
+
+ int choice = atoi(argv[3]);
+
+ if (choice > json_object_array_length(versions) - 1 || choice < 0)
+ {
+ printf("`%i' is not a valid ID\n\nrun `polecat wine list' to get a valid ID", choice);
+ }
+ else
+ {
+ struct json_object* url;
+ struct json_object* value = json_object_array_get_idx(versions, choice);
+
+ json_object_object_get_ex(value, "url", &url);
+ char* name = basename((char*)json_object_get_string(url));
+
+ printf("Downloading %s", name);
+ downloadFile(json_object_get_string(url), name);
+ printf("\nDone\n");
+ }
+ }
+ }
+ else
+ {
+ puts("Usage: polecat wine download <ID>\n\nIDs are obtained via `polecat wine list' ");
+ }
+ return 0;
+}
+
+int wine_list(int argc, char** argv)
+{
+ struct json_object* runner = fetchJSON(WINE_API);
+
+ if (runner)
+ {
+ struct json_object* versions, *value, *val;
+ json_object_object_get_ex(runner, "versions", &versions);
+
+ puts("Installable wine versions:");
+
+ for (size_t i = 0; i < json_object_array_length(versions); ++i)
+ {
+ value = json_object_array_get_idx(versions, i);
+ json_object_object_get_ex(value, "version", &val);
+ printf(" [%zu]\t%s\n", i, json_object_get_string(val));
+ }
+ }
+
+ return 0;
+}
+
+int wine_help(int argc, char** argv)
+{
+ puts("usage: polecat wine <command>\n\nList of commands:");
+
+ print_help(wine_commands, ARRAY_LEN(wine_commands));
+
+ return 0;
+} \ No newline at end of file
diff --git a/src/wine.h b/src/wine.h
new file mode 100644
index 0000000..872a17e
--- /dev/null
+++ b/src/wine.h
@@ -0,0 +1,9 @@
+#ifndef WINE_H
+#define WINE_H
+
+int wine(int, char**);
+int wine_install(int, char**);
+int wine_list(int, char**);
+int wine_help(int, char**);
+
+#endif \ No newline at end of file