#include "digest/digest.h" #include "macros.h" #include "l2su.h" #include #include #include #include #include #include #include #include #include #include #include /* handcoded string functions * * NOTE: I am aware that this is inefficient but since these are used only a handful of times * during initialization, I don't think performance is really a huge deal here. * * remember that this is a Minecraft launcher, so the PC has to meet the minimum specs of that, * which are well above the minimum specs of this launcher */ char *l2_launcher_strapp(char *buf, const char *src) { size_t buflen = strlen(buf); size_t srclen = strlen(src); char *ret = realloc(buf, buflen + srclen + 1); if (!ret) return NULL; memcpy(ret + buflen, src, srclen); ret[buflen + srclen] = '\0'; /* realloc does not initialize like calloc does */ return ret; } char *l2_launcher_sprintf_alloc(const char *fmt, ...) { va_list pva; va_start(pva, fmt); size_t len = vsnprintf(NULL, 0, fmt, pva); va_end(pva); char *ret = calloc(len + 1, sizeof(char)); if (!ret) { return ret; } va_start(pva, fmt); vsnprintf(ret, len + 1, fmt, pva); va_end(pva); return ret; } char *l2_launcher_find_config_path(void) { /* check for $L2SU_CONFIG */ char *config = getenv(PROJECT_NAME_UPPER "_CONFIG"); if (config) { return strdup(config); } /* check for $XDG_CONFIG_HOME */ config = getenv("XDG_CONFIG_HOME"); if (config) { /* want to append '"/" PROJECT_NAME' to our string */ char *ret = strdup(config); if (!ret) return NULL; return l2_launcher_strapp(ret, "/" PROJECT_NAME); } /* check for $HOME/.config */ config = getenv("HOME"); if (config) { char *ret = strdup(config); if (!ret) return NULL; return l2_launcher_strapp(ret, "/.config/" PROJECT_NAME); } /* fail (do NOT attempt to find home directory from passwd */ return NULL; } char *l2_launcher_find_data_path(void) { /* check for $L2SU_DATA */ char *config = getenv(PROJECT_NAME_UPPER "_DATA"); if (config) { return strdup(config); } /* check for $XDG_DATA_HOME */ config = getenv("XDG_DATA_HOME"); if (config) { char *ret = strdup(config); if (!ret) return NULL; return l2_launcher_strapp(ret, "/" PROJECT_NAME); } /* check for $HOME/.local/share */ config = getenv("HOME"); if (config) { char *ret = strdup(config); if (!ret) return NULL; return l2_launcher_strapp(ret, "/.local/share/" PROJECT_NAME); } return NULL; } int l2_launcher_open_config(const char *path, int flags, mode_t mode) { int conffd = open(l2_state.paths.config, O_RDONLY | O_DIRECTORY); if (conffd < 0) return INSTANCE_ERRNO; int instfd = openat(conffd, path, flags, mode); int en = errno; /* back up errno because close can fail */ close(conffd); errno = en; return instfd; } int l2_launcher_mkdir_parents(const char *path) { return l2_launcher_mkdir_parents_ex(path, 0); } /* NOTE: There's no portable (or otherwise - see open(2) BUGS) way to do this without race conditions. */ int l2_launcher_mkdir_parents_ex(const char *path, unsigned ignore) { if (*path != '/') return -1; char *pathbuf; size_t pathlen; L2_ASTRDUP(pathbuf, pathlen, path); char *pcurelem = pathbuf; for (char *cur = pathbuf + pathlen; ignore && cur != pathbuf; --cur) { if (*cur == '/') { --ignore; *cur = '\0'; } } struct stat stbuf = { 0 }; do { /* strtok is off-limits because it smells bad */ *pcurelem = '/'; pcurelem = strchr(pcurelem + 1, '/'); if (pcurelem) { *pcurelem = '\0'; } /* now pathbuf contains our truncated path name which may or may not exist */ if (mkdir(pathbuf, 0755) < 0) { if (errno == EEXIST) { /* racy: stat the file and continue if it is a directory */ if (stat(pathbuf, &stbuf) < 0) { return -1; } if (!S_ISDIR(stbuf.st_mode)) { return -1; } } else { return -1; } } } while (pcurelem); return 0; } char *l2_launcher_parse_iso_time(const char *str, struct tm *ts) { return strptime(str, "%FT%T%z", ts); /* TODO: replace with something portable */ } void l2_launcher_download_init(struct l2_dlbuf *buf) { buf->data = NULL; buf->size = 0; buf->capacity = 0; } size_t l2_launcher__download_callback(char *data, size_t size, size_t nmemb, void *user) { struct l2_dlbuf *buf = user; size_t realsz = size * nmemb; if (buf->size + realsz > buf->capacity) { size_t newcap = 16; while (newcap && buf->size + realsz > newcap) newcap <<= 1; if (!newcap) return CURLE_WRITE_ERROR; void *newbuf = realloc(buf->data, newcap); if (!newbuf) return CURLE_WRITE_ERROR; buf->data = newbuf; buf->capacity = newcap; } memcpy((char *)buf->data + buf->size, data, realsz); buf->size += realsz; return realsz; } void *l2_launcher_download_finalize(struct l2_dlbuf *buf, size_t *psz) { void *smaller = realloc(buf->data, buf->size); if (smaller) buf->data = smaller; void *data = buf->data; buf->data = NULL; *psz = buf->size; return data; } void l2_launcher_download_cleanup(struct l2_dlbuf *buf) { free(buf->data); } const curl_write_callback l2_dlcb = &l2_launcher__download_callback; CURLcode l2_launcher_download(CURL *cd, const char *url, void **odata, size_t *osize) { struct l2_dlbuf db; CURLcode code; l2_launcher_download_init(&db); curl_easy_setopt(cd, CURLOPT_URL, url); curl_easy_setopt(cd, CURLOPT_WRITEDATA, &db); curl_easy_setopt(cd, CURLOPT_WRITEFUNCTION, l2_dlcb); curl_easy_setopt(cd, CURLOPT_USERAGENT, L2_USER_AGENT); if ((code = curl_easy_perform(cd)) != CURLE_OK) { l2_launcher_download_cleanup(&db); return code; } *odata = l2_launcher_download_finalize(&db, osize); l2_launcher_download_cleanup(&db); /* not strictly necessary but ok */ return CURLE_OK; } int l2_json_merge_objects(json_t *j1, json_t *j2) { const char *key; size_t keylen; json_t *val; json_t *myval; json_object_keylen_foreach(j2, key, keylen, val) { myval = json_object_getn(j1, key, keylen); if (json_is_object(myval) && json_is_object(val)) { if (l2_json_merge_objects(myval, val) < 0) return -1; } else if (json_is_array(myval) && json_is_array(val)) { if (json_array_extend(myval, val) < 0) return -1; } else if (!myval) { if (json_object_setn_nocheck(j1, key, keylen, val) < 0) return -1; } } return 0; }