aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cmd-instance.c25
-rw-r--r--src/command.c239
-rw-r--r--src/command.h77
-rw-r--r--src/commands.h32
-rw-r--r--src/config.h.in7
-rw-r--r--src/instance.h16
-rw-r--r--src/l2su.c135
-rw-r--r--src/l2su.h17
-rw-r--r--src/macros.h8
-rw-r--r--src/main.c58
-rw-r--r--src/meson.build6
-rw-r--r--src/uuid/uuid.c123
-rw-r--r--src/uuid/uuid.h27
13 files changed, 770 insertions, 0 deletions
diff --git a/src/cmd-instance.c b/src/cmd-instance.c
new file mode 100644
index 0000000..9e346e2
--- /dev/null
+++ b/src/cmd-instance.c
@@ -0,0 +1,25 @@
+#include "commands.h"
+#include "src/command.h"
+#include <stdio.h>
+
+unsigned cmd_instance_add(struct l2_context_node *ctx, char **args)
+{
+ printf("add command yay\n");
+ return CMD_RESULT_SUCCESS;
+}
+
+unsigned cmd_instance_remove(struct l2_context_node *ctx, char **args)
+{
+ return CMD_RESULT_SUCCESS;
+}
+
+unsigned cmd_instance_list(struct l2_context_node *ctx, char **args)
+{
+return CMD_RESULT_SUCCESS;
+}
+
+unsigned cmd_instance_rename(struct l2_context_node *ctx, char **args)
+{
+ return CMD_RESULT_SUCCESS;
+}
+
diff --git a/src/command.c b/src/command.c
new file mode 100644
index 0000000..6be2fb5
--- /dev/null
+++ b/src/command.c
@@ -0,0 +1,239 @@
+#include "command.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#include "commands.h"
+
+#define CMD_ALIASES(...) (const char *[]){ __VA_ARGS__, NULL }
+
+struct l2_command_node l2_cmd_root = {
+ .type = CMD_NODE_TYPE_NULL,
+ .name = "",
+
+ .cmd_proc = NULL,
+
+ .children = (struct l2_command_node []) {
+ {
+ .type = CMD_NODE_TYPE_LITERAL,
+ .name = "instance",
+
+ .children = (struct l2_command_node []) {
+ {
+ .type = CMD_NODE_TYPE_LITERAL,
+ .name = "add",
+
+ .children = (struct l2_command_node []) {
+ {
+ .type = CMD_NODE_TYPE_ARGUMENT,
+ .name = "name",
+
+ .cmd_proc = &cmd_instance_add,
+
+ .children = (struct l2_command_node []) {
+ {
+ .type = CMD_NODE_TYPE_LITERAL,
+ .name = "at",
+
+ .children = (struct l2_command_node []) {
+ {
+ .type = CMD_NODE_TYPE_ARGUMENT,
+ .name = "location",
+
+ .cmd_proc = &cmd_instance_add
+ },
+ { 0 }
+ }
+ },
+ { 0 }
+ }
+ },
+ { 0 }
+ }
+ },
+ {
+ .type = CMD_NODE_TYPE_LITERAL,
+ .name = "remove",
+
+ .children = (struct l2_command_node []) {
+ {
+ .type = CMD_NODE_TYPE_ARGUMENT,
+ .name = "name",
+
+ .cmd_proc = &cmd_instance_remove
+ },
+ { 0 }
+ }
+ },
+ {
+ .type = CMD_NODE_TYPE_LITERAL,
+ .name = "list",
+
+ .cmd_proc = &cmd_instance_list
+ },
+ {
+ .type = CMD_NODE_TYPE_LITERAL,
+ .name = "rename",
+
+ .children = (struct l2_command_node []) {
+ {
+ .type = CMD_NODE_TYPE_ARGUMENT,
+ .name = "oldname",
+
+ .children = (struct l2_command_node []) {
+ {
+ .type = CMD_NODE_TYPE_ARGUMENT,
+ .name = "newname",
+
+ .cmd_proc = &cmd_instance_rename
+ },
+ { 0 }
+ }
+ },
+ { 0 }
+ }
+ },
+ { 0 }
+ }
+ },
+ { 0 }
+ }
+};
+
+bool l2_cmd__parse_command_int(char **argvcur, struct l2_command_node *start, struct l2_context_node **ctx, struct l2_parseinfo *parseinfo);
+
+unsigned l2_cmd_parse_command(char **argv, struct l2_parseinfo *parseinfo)
+{
+ if (!argv || !parseinfo) {
+ return CMD_PARSE_EUSAGE;
+ }
+
+ l2_cmd__parse_command_int(argv, &l2_cmd_root, &parseinfo->ctx, parseinfo);
+
+ return parseinfo->ctx ? CMD_PARSE_SUCCESS : CMD_PARSE_ESYNTAX;
+}
+
+bool l2_cmd__parse_command_int(char **argvcur, struct l2_command_node *start, struct l2_context_node **ctx, struct l2_parseinfo *parseinfo)
+{
+ struct l2_command_node *curchild;
+ struct l2_context_node *childctx = NULL;
+ unsigned argresult;
+
+ *ctx = NULL;
+
+ if (!*argvcur) {
+ return true;
+ }
+
+ switch (start->type) {
+ case CMD_NODE_TYPE_NULL:
+ curchild = start->children;
+
+ while (curchild && curchild->name && !*ctx) {
+ l2_cmd__parse_command_int(argvcur, curchild, ctx, parseinfo);
+ ++curchild;
+ }
+
+ return true;
+ case CMD_NODE_TYPE_LITERAL:
+ if (strcmp(*argvcur, start->name)) {
+ if (!start->extra.aliases) return true; /* no aliases */
+
+ bool found = false;
+ for (const char **alias = start->extra.aliases; *alias; ++alias) {
+ if (!strcmp(*argvcur, *alias)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) return true;
+ }
+
+ break;
+ case CMD_NODE_TYPE_ARGUMENT:
+ argresult = start->extra.validate_proc ? (start->extra.validate_proc)(*argvcur) : CMD_VALIDATE_PASS;
+ if (argresult != CMD_VALIDATE_PASS)
+ return argresult == CMD_VALIDATE_FAIL_SKIP;
+ break;
+ default:
+ abort();
+ }
+
+ curchild = start->children;
+ while (curchild && curchild->name && !childctx) {
+ if (!l2_cmd__parse_command_int(argvcur + 1, curchild, &childctx, parseinfo)) break;
+ ++curchild;
+ }
+
+ if (childctx || start->cmd_proc) { /* we've found a promising child node (or we're executable) */
+ size_t arglen = strlen(*argvcur);
+ struct l2_context_node *mynode = calloc(1, sizeof(struct l2_context_node) + arglen);
+ mynode->node = start;
+ mynode->next = childctx;
+ mynode->value = *argvcur;
+
+ *ctx = mynode;
+
+ if (!childctx) { /* we're the executable node */
+ parseinfo->argv = argvcur + 1;
+ parseinfo->proc = start->cmd_proc;
+ }
+ }
+
+ return true;
+}
+
+void l2_cmd_free_ctx(struct l2_context_node *ctx)
+{
+ if (!ctx) return;
+
+ struct l2_context_node *next;
+ do {
+ next = ctx->next;
+ free(ctx);
+ ctx = next;
+ } while (ctx);
+}
+
+#if 0
+#include "uuid/uuid.h"
+#include <inttypes.h>
+
+int main(int argc, char **argv)
+{
+ char **argvcur = argv + 1;
+
+ if (!*argvcur) {
+ printf("done\n");
+ return 1;
+ }
+
+ ((void)argc);
+
+ struct l2_context_node *cur;
+ struct l2_parseinfo parseinfo;
+ memset(&parseinfo, 0, sizeof(struct l2_parseinfo));
+
+ unsigned res = l2_cmd_parse_command(argv + 1, &parseinfo);
+
+ printf("%p %s\n", (void *)parseinfo.ctx, parseinfo.argv ? *parseinfo.argv : "(null)");
+
+ if (res == CMD_PARSE_SUCCESS) {
+ cur = parseinfo.ctx;
+ while (cur) {
+ printf("%s\n", cur->value);
+ cur = cur->next;
+ }
+
+ printf("command returned: %u\n", (parseinfo.proc)(parseinfo.ctx, parseinfo.argv));
+
+ l2_cmd_free_ctx(parseinfo.ctx);
+ }
+
+ return 0;
+}
+#endif
diff --git a/src/command.h b/src/command.h
new file mode 100644
index 0000000..469422c
--- /dev/null
+++ b/src/command.h
@@ -0,0 +1,77 @@
+#ifndef L2SU_COMMAND_H_INCLUDED
+#define L2SU_COMMAND_H_INCLUDED
+
+#include <stddef.h>
+
+enum {
+ CMD_NODE_TYPE_NULL,
+ CMD_NODE_TYPE_LITERAL,
+ CMD_NODE_TYPE_ARGUMENT,
+ CMD_NODE_FLAG_EXEC = 0x100
+};
+
+enum {
+ CMD_INIT_SUCCESS,
+ CMD_INIT_ERROR
+};
+
+enum {
+ CMD_RESULT_SUCCESS,
+ CMD_RESULT_FAIL
+};
+
+enum {
+ CMD_VALIDATE_PASS, /* yep this argument is correct */
+ CMD_VALIDATE_FAIL_SKIP, /* this argument is NOT correct */
+ CMD_VALIDATE_FAIL_ERROR /* this argument IS the correct argument, but it is invalid (stop searching siblings) */
+};
+
+enum {
+ CMD_PARSE_SUCCESS, /* parse successful */
+ CMD_PARSE_ESYNTAX, /* invalid command syntax */
+ CMD_PARSE_EUSAGE /* function was called with NULL arguments */
+};
+
+struct l2_command_node;
+
+struct l2_context_node {
+ struct l2_context_node *next;
+ struct l2_command_node *node;
+
+ char *value; /* for argument command nodes */
+};
+
+typedef unsigned (l2_cmd_proc_t)(struct l2_context_node * /* ctx */, char ** /* args */);
+typedef unsigned (l2_cmd_validate_proc_t)(const char * /* arg */);
+
+struct l2_command_node {
+ unsigned type;
+ const char *name;
+
+ union {
+ /* for literal nodes */
+ const char **aliases;
+
+ /* for argument nodes */
+ l2_cmd_validate_proc_t *validate_proc; /* a validate_proc of NULL is assumed to always return CMD_VALIDATE_PASS */
+ } extra;
+
+ l2_cmd_proc_t *cmd_proc;
+
+ struct l2_command_node *children;
+};
+
+extern struct l2_command_node l2_cmd_root;
+
+/* unsigned l2_cmd_init(void); */
+
+struct l2_parseinfo {
+ char **argv;
+ l2_cmd_proc_t *proc;
+ struct l2_context_node *ctx;
+};
+
+unsigned l2_cmd_parse_command(char **argv, struct l2_parseinfo *parseinfo);
+void l2_cmd_free_ctx(struct l2_context_node *ctx);
+
+#endif /* include guard */
diff --git a/src/commands.h b/src/commands.h
new file mode 100644
index 0000000..fe3f102
--- /dev/null
+++ b/src/commands.h
@@ -0,0 +1,32 @@
+#ifndef L2SU_COMMANDS_H_INCLUDED
+#define L2SU_COMMANDS_H_INCLUDED
+
+#include "command.h"
+
+unsigned cmd_instance_add(struct l2_context_node *, char **);
+unsigned cmd_instance_remove(struct l2_context_node *, char **);
+unsigned cmd_instance_list(struct l2_context_node *, char **);
+unsigned cmd_instance_rename(struct l2_context_node *, char **);
+
+unsigned cmd_user_add(struct l2_context_node *, char **);
+unsigned cmd_user_remove(struct l2_context_node *, char **);
+unsigned cmd_user_nick(struct l2_context_node *, char **);
+
+unsigned cmd_version_list_remote(struct l2_context_node *, char **);
+unsigned cmd_version_list_local(struct l2_context_node *, char **);
+unsigned cmd_version_install(struct l2_context_node *, char **);
+
+unsigned cmd_profile_add(struct l2_context_node *, char **);
+unsigned cmd_profile_delete(struct l2_context_node *, char **);
+unsigned cmd_profile_rename(struct l2_context_node *, char **);
+unsigned cmd_profile_set(struct l2_context_node *, char **);
+unsigned cmd_profile_get(struct l2_context_node *, char **);
+unsigned cmd_profile_info(struct l2_context_node *, char **);
+
+unsigned cmd_launch(struct l2_context_node *, char **);
+unsigned cmd_launch2(struct l2_context_node *, char **);
+
+unsigned cmd_version(struct l2_context_node *, char **);
+unsigned cmd_help(struct l2_context_node *, char **);
+
+#endif /* include guard */
diff --git a/src/config.h.in b/src/config.h.in
new file mode 100644
index 0000000..05e3045
--- /dev/null
+++ b/src/config.h.in
@@ -0,0 +1,7 @@
+#ifndef L2SU_CONFIG_H_INCLUDED
+#define L2SU_CONFIG_H_INCLUDED
+
+#mesondefine PROJECT_NAME
+#mesondefine PROJECT_NAME_UPPER
+
+#endif /* include guard */
diff --git a/src/instance.h b/src/instance.h
new file mode 100644
index 0000000..b0be665
--- /dev/null
+++ b/src/instance.h
@@ -0,0 +1,16 @@
+#ifndef L2SU_INSTANCE_H_INCLUDED
+#define L2SU_INSTANCE_H_INCLUDED
+
+#include "uuid/uuid.h"
+
+struct l2_instance {
+ uuid_t uuid;
+ char *name;
+ char *path;
+};
+
+void l2_instance_load(void);
+
+void l2_instance_save(void);
+
+#endif /* include guard */
diff --git a/src/l2su.c b/src/l2su.c
new file mode 100644
index 0000000..bd22987
--- /dev/null
+++ b/src/l2su.c
@@ -0,0 +1,135 @@
+#include "l2su.h"
+#include "macros.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+char *l2_launcher__strdup(const char *);
+char *l2_launcher__strapp(char *, const char *);
+
+char *l2_launcher__find_config_path(void)
+{
+ /* check for $L2SU_CONFIG */
+ char *config = getenv(PROJECT_NAME_UPPER "_CONFIG");
+ if (config) {
+ return l2_launcher__strdup(config);
+ }
+
+ /* check for $XDG_CONFIG_HOME */
+ config = getenv("XDG_CONFIG_HOME");
+ if (config) {
+ /* want to append '"/" PROJECT_NAME' to our string */
+ char *ret = l2_launcher__strdup(config);
+ if (!ret) return NULL;
+
+ return l2_launcher__strapp(ret, "/" PROJECT_NAME);
+ }
+
+ /* check for $HOME/.config */
+ config = getenv("HOME");
+ if (config) {
+ char *ret = l2_launcher__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 l2_launcher__strdup(config);
+ }
+
+ /* check for $XDG_DATA_HOME */
+ config = getenv("XDG_DATA_HOME");
+ if (config) {
+ char *ret = l2_launcher__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 = l2_launcher__strdup(config);
+ if (!ret) return NULL;
+
+ return l2_launcher__strapp(ret, "/.local/share/" PROJECT_NAME);
+ }
+
+ return NULL;
+}
+
+int l2_launcher_init(void)
+{
+ /* find paths (set up l2_state.paths) */
+ /* create folders if they don't exist */
+
+ printf("project is " PROJECT_NAME "...\n");
+
+ /* look for config path */
+ if (!(l2_state.paths.config = l2_launcher__find_config_path())) {
+ fputs("fatal: Could not decide on a configuration directory! Please define " PROJECT_NAME_UPPER "_CONFIG to a sensible value.\n", stderr);
+ return 1;
+ }
+
+ if (!(l2_state.paths.data = l2_launcher__find_data_path())) {
+ fputs("fatal: Could not decide on a data directory! Please define " PROJECT_NAME_UPPER "_DATA to a sensible value.\n", stderr);
+ return 1;
+ }
+
+ printf("config path: %s\n", l2_state.paths.config);
+ printf("data path: %s\n", l2_state.paths.data);
+
+ return 0;
+}
+
+int main(void)
+{
+ int status = l2_launcher_init();
+ if (status != 0) return status;
+
+ return 0;
+}
+
+/* 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__strdup(const char *in)
+{
+ if (!in) return NULL;
+
+ size_t inlen = strlen(in);
+ char *ret = calloc(1, inlen + 1);
+
+ if (ret) memcpy(ret, in, inlen);
+
+ return ret;
+}
+
+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';
+
+ return ret;
+}
diff --git a/src/l2su.h b/src/l2su.h
new file mode 100644
index 0000000..13df852
--- /dev/null
+++ b/src/l2su.h
@@ -0,0 +1,17 @@
+#ifndef L2SU_MAIN_H_INCLUDED
+#define L2SU_MAIN_H_INCLUDED
+
+#include "command.h"
+#include "instance.h"
+
+struct l2_user_paths {
+ const char *config;
+ const char *data;
+};
+
+struct {
+ struct l2_user_paths paths;
+ struct l2_instance *instances;
+} l2_state;
+
+#endif /* include guard */
diff --git a/src/macros.h b/src/macros.h
new file mode 100644
index 0000000..771032c
--- /dev/null
+++ b/src/macros.h
@@ -0,0 +1,8 @@
+#ifndef L2SU_MACROS_H_INCLUDED
+#define L2SU_MACROS_H_INCLUDED
+
+#include "config.h"
+
+#define L2_ARRLEN(_a) (sizeof(_a) / sizeof(*(_a)))
+
+#endif /* include guard */
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..958f0b0
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,58 @@
+#include <stdio.h>
+
+#if 0
+struct res_buf {
+ void *data;
+ size_t len, cap;
+};
+
+size_t launcher_write_response(char *ptr, size_t size, size_t nmemb, void *userdata)
+{
+ ((void)size);
+ struct res_buf *rb = userdata;
+ if (nmemb == 0) return 0;
+
+ /* it is documented that size is always 1 */
+
+ if (rb->len + nmemb > rb->cap) {
+ size_t newcap = rb->cap;
+ while (newcap < rb->len + nmemb) newcap <<= 1;
+ rb->data = realloc(rb->data, newcap); /* assume it worked lol */
+ rb->cap = newcap;
+ }
+
+ memcpy((unsigned char *)rb->data + rb->len, ptr, nmemb);
+ rb->len += nmemb;
+ return nmemb;
+}
+
+int main(void)
+{
+ CURL *c = curl_easy_init();
+ struct res_buf rb;
+
+ rb.data = malloc(16);
+ rb.len = 0;
+ rb.cap = 16;
+
+ curl_easy_setopt(c, CURLOPT_URL, "https://launchermeta.mojang.com/mc/game/version_manifest_v2.json");
+ curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, &launcher_write_response);
+ curl_easy_setopt(c, CURLOPT_WRITEDATA, &rb);
+
+ CURLcode res = curl_easy_perform(c);
+ if (res != CURLE_OK) {
+ fprintf(stderr, "%s\n", "uh oh curl failed :((((");
+ return 1;
+ }
+
+ curl_easy_cleanup(c);
+
+
+ printf("%s\n", "amogus amogus :))))");
+ printf("%zu %zu\n", rb.len, rb.cap);
+
+ fwrite(rb.data, rb.len, 1, stdout);
+ free(rb.data);
+ return 0;
+}
+#endif
diff --git a/src/meson.build b/src/meson.build
new file mode 100644
index 0000000..2f675ea
--- /dev/null
+++ b/src/meson.build
@@ -0,0 +1,6 @@
+launcher_srcs = files('l2su.c', 'command.c', 'cmd-instance.c', 'uuid/uuid.c')
+
+configure_file(input : 'config.h.in', output : 'config.h', configuration : config_data)
+config_include_dir = include_directories('.')
+
+
diff --git a/src/uuid/uuid.c b/src/uuid/uuid.c
new file mode 100644
index 0000000..0d49f0b
--- /dev/null
+++ b/src/uuid/uuid.c
@@ -0,0 +1,123 @@
+#include "uuid.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/random.h> /* not very portable but idc */
+
+bool l2__str_to_uint64(uint64_t *out, const char *str);
+
+void l2_uuid__set_version(uuid_t *id, unsigned version)
+{
+ version &= 0x0F;
+
+ id->halves[1] &= ~(UINT64_C(0x0F) << 12);
+ id->halves[1] |= (uint64_t)version << 12;
+}
+
+void l2_uuid__set_variant(uuid_t *id, unsigned variant, unsigned bits)
+{
+ uint64_t mask = (UINT64_C(1) << (64 - bits)) - 1;
+ uint64_t lvariant = (uint64_t)variant << (64 - bits);
+ lvariant &= ~mask;
+
+ id->halves[0] &= mask;
+ id->halves[0] |= lvariant;
+}
+
+void l2_uuid_random(uuid_t *id)
+{
+ getentropy(id, sizeof(uuid_t));
+ l2_uuid__set_version(id, 4);
+ l2_uuid__set_variant(id, 2, 2);
+}
+
+void l2_uuid_to_string(const uuid_t *id, char *out)
+{
+ snprintf(out, UUID_STRLEN + 1, "%08" PRIx64 "-%04" PRIx64 "-%04" PRIx64 "-%04" PRIx64 "-%012" PRIx64,
+ id->halves[1] >> 32, /* time-low */
+ id->halves[1] >> 16 & 0xffff, /* time-mid */
+ id->halves[1] & 0xffff, /* time-high-and-version */
+ id->halves[0] >> 48, /* clock-seq-and-reserved, clock-seq-low */
+ id->halves[0] & UINT64_C(0xffffffffffff)); /* node */
+}
+
+void l2_uuid_to_string_short(const uuid_t *id, char *out)
+{
+ snprintf(out, UUID_STRLEN_SHORT + 1, "%08" PRIx64 "%08" PRIx64, id->halves[1], id->halves[0]);
+}
+
+bool l2_uuid_from_string(uuid_t *id, const char *str)
+{
+ size_t slen = strlen(str);
+ char rbuf[UUID_STRLEN_SHORT + 1];
+
+ if (slen != UUID_STRLEN) return false;
+
+ memcpy(rbuf, str, 8);
+ memcpy(rbuf + 8, str + 9, 4);
+ memcpy(rbuf + 12, str + 14, 4);
+ memcpy(rbuf + 16, str + 19, 4);
+ memcpy(rbuf + 20, str + 24, 12);
+ rbuf[UUID_STRLEN_SHORT] = '\0';
+
+ return l2_uuid_from_string_short(id, rbuf);
+}
+
+bool l2_uuid_from_string_short(uuid_t *id, const char *str)
+{
+ size_t slen = strlen(str);
+ uuid_t outid;
+
+ if (slen != UUID_STRLEN_SHORT) return false;
+
+ if (!l2__str_to_uint64(outid.halves + 1, str)) return false;
+ if (!l2__str_to_uint64(outid.halves, str + 16)) return false;
+
+ memcpy(id, &outid, sizeof(uuid_t));
+ return true;
+}
+
+/* This function exists because there's not a portable way to do this.
+ * Don't suggest strtoull, because uint64_t may not be a ulonglong. */
+bool l2__str_to_uint64(uint64_t *out, const char *str)
+{
+ *out = 0;
+ str += 15;
+ for (unsigned i = 0; i < 16; ++i, --str) {
+ switch (*str) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ *out |= (uint64_t)(*str - '0') << (i * 4);
+ break;
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ *out |= (uint64_t)(*str - 'a' + 10) << (i * 4);
+ break;
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ *out |= (uint64_t)(*str - 'A' + 10) << (i * 4);
+ break;
+ default:
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/src/uuid/uuid.h b/src/uuid/uuid.h
new file mode 100644
index 0000000..740fa17
--- /dev/null
+++ b/src/uuid/uuid.h
@@ -0,0 +1,27 @@
+#ifndef L2SU_UUID_H_INCLUDED
+#define L2SU_UUID_H_INCLUDED
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/* A 128-bit GUID as specified by RFC 4122.
+ * NOTE: halves[0] is the least significant half of the GUID. */
+typedef union tag_uuid_t {
+ uint8_t bytes[16];
+ uint64_t halves[2];
+} uuid_t;
+
+#define UUID_NULL_INIT { { 0 } }
+
+#define UUID_STRLEN (36u)
+#define UUID_STRLEN_SHORT (32u)
+
+void l2_uuid_random(uuid_t *id);
+
+void l2_uuid_to_string(const uuid_t *id, char *out);
+void l2_uuid_to_string_short(const uuid_t *id, char *out);
+
+bool l2_uuid_from_string(uuid_t *id, const char *str);
+bool l2_uuid_from_string_short(uuid_t *id, const char *str);
+
+#endif /* include guard */