From 20d9fc5b5356a1054cb104ba6651b77184abf0db Mon Sep 17 00:00:00 2001 From: bigfoot547 Date: Sun, 24 Dec 2023 04:26:00 -0600 Subject: initial commit --- .gitignore | 3 + README.md | 2 + USAGE.md | 40 +++++++++ meson.build | 12 +++ src/cmd-instance.c | 25 ++++++ src/command.c | 239 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/command.h | 77 +++++++++++++++++ src/commands.h | 32 +++++++ src/config.h.in | 7 ++ src/instance.h | 16 ++++ src/l2su.c | 135 ++++++++++++++++++++++++++++++ src/l2su.h | 17 ++++ src/macros.h | 8 ++ src/main.c | 58 +++++++++++++ src/meson.build | 6 ++ src/uuid/uuid.c | 123 +++++++++++++++++++++++++++ src/uuid/uuid.h | 27 ++++++ 17 files changed, 827 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 USAGE.md create mode 100644 meson.build create mode 100644 src/cmd-instance.c create mode 100644 src/command.c create mode 100644 src/command.h create mode 100644 src/commands.h create mode 100644 src/config.h.in create mode 100644 src/instance.h create mode 100644 src/l2su.c create mode 100644 src/l2su.h create mode 100644 src/macros.h create mode 100644 src/main.c create mode 100644 src/meson.build create mode 100644 src/uuid/uuid.c create mode 100644 src/uuid/uuid.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f014aae --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.cache/* +builddir/* +compile_commands.json diff --git a/README.md b/README.md new file mode 100644 index 0000000..4792b10 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# Launcher 2 SU +If OLauncher was so good, why is there no Launcher 2? diff --git a/USAGE.md b/USAGE.md new file mode 100644 index 0000000..802cf3e --- /dev/null +++ b/USAGE.md @@ -0,0 +1,40 @@ +- `l2su instance add [at ] [--force]` +- `l2su instance list` +- `l2su instance remove [--delete]` +- `l2su instance rename ` + +- `l2su user add` +- `l2su user remove ` +- `l2su user nick []` - handy for users with long annoying names like `______HAHA______` + +- `l2su version list remote` +- `l2su version list local` +- `l2su version install ` + +- `l2su profile add ` +- `l2su profile delete ` +- `l2su profile rename ` +- `l2su profile set ` +- `l2su profile get ` +- `l2su profile info ` + +- `l2su launch [] [--force] [--detach] [--nodetach] [-- []] [++ []]` + - `--force`: launches without interaction, even if offline + - `--detach`: prevents the launcher from staying open (despite profile settings, this is the default) + - `--nodetach`: keeps the launcher running and printing game logs to stdout/stderr + - ``: arguments passed without interpretation (besides checking for `++`) to the JVM + - ``: arguments passed without interpretation to the game +- `l2su launch2 [--force] [--detach] [--nodetach] [-- []] [++ []]` + +- `l2su version` +- `l2su help` + +# profile settings +- `instance`: the instance that this profile +- `version`: the version of the game to use +- `user`: a user that should be the default selected when using `l2su launch` +- `vm-opts`: VM options to use for the profile +- `runtime`: `null` for mojang-provided launcher, or an absolute path to a java runtime to use +- `launch-method`: `jni` or `traditional` +- `detach`: `true` to return control back to the shell after launching the game successfully +- `resolution`: a value like `1280x720` to pass to the game as a custom resolution value diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..073c348 --- /dev/null +++ b/meson.build @@ -0,0 +1,12 @@ +project('l2su', 'c') + +curl_dep = dependency('libcurl') +jansson_dep = dependency('jansson') + +config_data = configuration_data() + +config_data.set_quoted('PROJECT_NAME', 'l2su') +config_data.set_quoted('PROJECT_NAME_UPPER', 'L2SU') + +subdir('src') +executable('l2su', launcher_srcs, dependencies : [curl_dep, jansson_dep], include_directories : [config_include_dir], override_options : {'c_std': 'c99'}) 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 + +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 +#include +#include +#include +#include + +#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 + +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 + +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 +#include +#include + +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 + +#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 +#include +#include +#include /* 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 +#include + +/* 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 */ -- cgit v1.2.3-70-g09d2