diff --git a/README.md b/README.md
index 145e5a6..3126a45 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,7 @@
- **hd** - makes `
` headings
- **yandere** - prints a yandere-like sentence *ad infinitum*
- **iw** - a helper program to make a message like an installation wizard to stdout
+- **ud** - a fake updater
## Installation
diff --git a/ud/README.md b/ud/README.md
new file mode 100644
index 0000000..330016e
--- /dev/null
+++ b/ud/README.md
@@ -0,0 +1,15 @@
+## Introduction
+
+`ud` is a fake updater.
+
+```bash
+$ ud -m 마인크래프트해킹버전1.21.3 -m 메소복사버그v2 -i8
+remove: 메소복사버그v2.zip
+extract: 마인크래프트해킹버전1.21.3.jpeg
+archive: 마인크래프트해킹버전1.21.3.txt
+build: 메소복사버그v2.zip
+archive: 메소복사버그v2.obj
+remove: 메소복사버그v2.obj
+download: 마인크래프트해킹버전1.21.3.obj
+archive: 메소복사버그v2.dll
+```
diff --git a/ud/c/.gitignore b/ud/c/.gitignore
new file mode 100644
index 0000000..d4fee75
--- /dev/null
+++ b/ud/c/.gitignore
@@ -0,0 +1,5 @@
+ud
+ud.exe
+
+*.o
+*.d
diff --git a/ud/c/Makefile b/ud/c/Makefile
new file mode 100644
index 0000000..b224ea6
--- /dev/null
+++ b/ud/c/Makefile
@@ -0,0 +1,42 @@
+## Variables ##
+TARGET := ud
+
+CC := gcc
+CFLAGS := -O -Wall -W -pedantic
+CFLAGS += -g --std=c2x
+CFLAGS += -MMD -MP
+CPPFLAGS = \
+ -I $(DIR_LIB) \
+ -DPROGNAME='"$(TARGET)"' \
+ $(if $(DIR_DATA),-DDIR_DATA='"$(DIR_DATA)"')
+
+SOURCES = $(wildcard *.c)
+LIB_SRCS = $(wildcard $(DIR_LIB)/*.c)
+OBJECTS = $(SOURCES:.c=.o)
+LIB_OBJS = $(notdir $(LIB_SRCS:.c=.o))
+
+DIR_LIB := ../../lib
+
+## Targets ##
+.PHONY: all clean
+
+## Default Goal ##
+all: $(TARGET)
+
+## Build ##
+$(TARGET): $(OBJECTS) $(LIB_OBJS)
+ $(CC) $^ $(CFLAGS) -o $@
+
+$(OBJECTS): %.o: %.c
+ $(CC) $< $(CPPFLAGS) $(CFLAGS) -c -o $@
+
+$(LIB_OBJS): %.o: $(DIR_LIB)/%.c
+ $(CC) $< $(CPPFLAGS) $(CFLAGS) -c -o $@
+
+## Dependency ##
+-include $(OBJECTS:.o=.d)
+-include $(LIB_OBJS:.o=.d)
+
+## Tasks ##
+clean:
+ rm -f $(TARGET) $(TARGET).exe *.o *.d
diff --git a/ud/c/argp_drivers.c b/ud/c/argp_drivers.c
new file mode 100644
index 0000000..46fc2c7
--- /dev/null
+++ b/ud/c/argp_drivers.c
@@ -0,0 +1,118 @@
+#include "argp_drivers.h"
+#include "argp_drivers.internals.h"
+
+static bool drive_help(argtok_t *tok, argerr_t *err) {
+ (void) tok, (void) err;
+ cfg.help = true;
+ return true;
+}
+
+static bool drive_msg(argtok_t *tok, argerr_t *err) {
+ (void) err;
+ /* +1 for \0 */
+ array_append(cfg.msgs, tok->value, strlen(tok->value) + 1);
+ return true;
+}
+
+static bool drive_iter(argtok_t *tok, argerr_t *err) {
+ char *i;
+ unsigned long ret;
+
+ err->errhandle = argp_errhandle_default;
+
+ if (tok->value[strspn(tok->value, " \t\n\r\v\f")] == '-') {
+ err->reason = "iteration must not be negative";
+ return false;
+ }
+
+ errno = 0;
+ ret = strtoul(tok->value, &i, 10);
+
+ if (errno == ERANGE) {
+ err->reason = "out of range";
+ return false;
+ }
+ if (i == tok->value) {
+ err->reason = "conversion not performed";
+ return false;
+ }
+ if (*i != '\0') {
+ err->reason = "invalid trailing characters";
+ return false;
+ }
+ if (ret > SIZE_MAX) {
+ err->reason = "iteration out of range";
+ return false;
+ }
+
+ cfg.iter = ret;
+ return true;
+}
+
+static bool drive_lang(argtok_t *tok, argerr_t *err) {
+ typedef struct {
+ const char *lang;
+ langopt_enum_t enum_val;
+ } mapper_t;
+
+ static const mapper_t accept_set[] = {
+ { "en", LangoptEnumEn },
+ { "ko", LangoptEnumKo }
+ };
+ static const size_t accept_set_len = ARRLEN(accept_set);
+
+ const mapper_t *map;
+
+ for (size_t i = 0; i < accept_set_len; i++) {
+ map = &accept_set[i];
+ if (!strcmp(tok->value, map->lang)) {
+ cfg.lang = map->enum_val;
+ return true;
+ }
+ }
+
+ /* error path */
+ static const errhandle_unrecognized_value_ctx_t ctx = {
+ .targets = (const void **) &accept_set,
+ .targets_len = accept_set_len,
+ .target_siz = ESIZ(accept_set)
+ };
+
+ err->errhandle = errhandle_unrecognized_value;
+ err->ctx = &ctx;
+ err->ctx_siz = sizeof ctx;
+ return false;
+}
+
+static void errhandle_unrecognized_value(argtok_t *tok, const char *reason, void *ctx) {
+ find_alikes_ret_t ret;
+ errhandle_unrecognized_value_ctx_t *ectx;
+
+ (void) reason;
+ ectx = ctx;
+
+ ret = find_alikes(
+ tok->value,
+ ectx->targets,
+ ectx->targets_len,
+ ectx->target_siz
+ );
+
+ safe_vprintf(
+ "%s: unrecognized value " Cbwhite "'%s'" Creset ".",
+ tok->field, tok->value
+ );
+ safe_vprintf(" Do you mean ");
+ if (ret.nmemb == 1) {
+ safe_vprintf(Cbgreen "'%s'" Creset "?\n", ret.alikes[0]);
+ }
+ else {
+ safe_vprintf("one of the following?\n\t" Cbgreen);
+ for (size_t i = 0; i < ret.nmemb; i++) {
+ safe_vprintf("%s ", ret.alikes[i]);
+ }
+ safe_vprintf(Creset "\n");
+ }
+
+ free(ret.alikes);
+}
diff --git a/ud/c/argp_drivers.h b/ud/c/argp_drivers.h
new file mode 100644
index 0000000..96d71c2
--- /dev/null
+++ b/ud/c/argp_drivers.h
@@ -0,0 +1,9 @@
+#ifndef ARGP_DRIVERS_H
+#define ARGP_DRIVERS_H
+
+#include "argparse.h"
+
+extern optmd_t optmds[];
+extern const size_t optmds_len;
+
+#endif
diff --git a/ud/c/argp_drivers.internals.h b/ud/c/argp_drivers.internals.h
new file mode 100644
index 0000000..ab6fee9
--- /dev/null
+++ b/ud/c/argp_drivers.internals.h
@@ -0,0 +1,66 @@
+#ifndef ARGP_DRIVERS_INTERNALS_H
+#define ARGP_DRIVERS_INTERNALS_H
+
+#include
+#include
+
+#include "common.h"
+#include "config.h"
+#include "wrappers.h"
+#include "strutils.h"
+#include "colorcodes.h"
+#include "argp_drivers.h"
+
+typedef struct {
+ const void **targets;
+ size_t targets_len;
+ size_t target_siz;
+} errhandle_unrecognized_value_ctx_t;
+
+static opt_driver_t drive_help;
+static opt_driver_t drive_msg;
+static opt_driver_t drive_iter;
+static opt_driver_t drive_lang;
+
+static opt_errhandler_t errhandle_unrecognized_value;
+
+/*==================*
+ | EXTERN VARIABLES |
+ *==================*/
+optmd_t optmds[] = {
+ {
+ .sname = 'h',
+ .lname = "help",
+ .drive = drive_help,
+ .reqv = false,
+ .help = "prints the manual page",
+ .unique = true
+ },
+ {
+ .sname = 'm',
+ .lname = "msg",
+ .drive = drive_msg,
+ .reqv = true,
+ .help = "specify the message to print out",
+ .unique = false
+ },
+ {
+ .sname = 'i',
+ .lname = "iterate",
+ .drive = drive_iter,
+ .reqv = true,
+ .help = "specify the number of iteration",
+ .unique = true
+ },
+ {
+ .sname = '\0',
+ .lname = "lang",
+ .drive = drive_lang,
+ .reqv = true,
+ .help = "specify the language to speak",
+ .unique = true
+ }
+};
+const size_t optmds_len = ARRLEN(optmds);
+
+#endif
diff --git a/ud/c/config.c b/ud/c/config.c
new file mode 100644
index 0000000..69e9eae
--- /dev/null
+++ b/ud/c/config.c
@@ -0,0 +1,84 @@
+#include "config.h"
+#include "config.internals.h"
+
+extern void init_config(config_t *cfg) {
+ cfg->srcs = array_create(NULL);
+ cfg->msgs = array_create(NULL);
+ cfg->iter = 8;
+ cfg->lang = LangoptEnumEn;
+}
+
+extern void destroy_config(config_t *cfg) {
+ array_destroy(cfg->srcs);
+ array_destroy(cfg->msgs);
+}
+
+extern config_statcode_t apply_config(config_t *cfg) {
+ config_statcode_t ret;
+
+ if ((ret = check_help(cfg)) != ConfigStatcodeOk)
+ goto cleanup;
+ if ((ret = check_msg(cfg)) != ConfigStatcodeOk)
+ goto cleanup;
+
+cleanup: /* nothing to clean up, just return */
+ return ret;
+}
+
+static config_statcode_t check_help(config_t *cfg) {
+ if (cfg->help == false) {
+ return ConfigStatcodeOk;
+ }
+
+ return print_manpage();
+}
+
+static config_statcode_t check_msg(config_t *cfg) {
+ if (array_size(cfg->msgs) > 0) {
+ return ConfigStatcodeOk;
+ }
+
+ fprintf(stderr, ENPREFIX "--msg is a required flag. Terminating.\n");
+ return ConfigStatcodeNoMsg;
+}
+
+static config_statcode_t print_manpage(void) {
+#if !defined(_WIN32) && (defined(__unix__) || defined(__unix))
+ if (!system(NULL)) {
+ return print_manual_fallback();
+ }
+
+ if (system("man " DIR_MANPAGE)) {
+ return print_manual_fallback();
+ }
+
+ return ConfigStatcodeNormalExit;
+#else
+ return print_manual_fallback();
+#endif
+}
+
+static config_statcode_t print_manual_fallback(void) {
+ FILE *fp;
+ int ch;
+
+ fp = fopen(DIR_MANPAGE_TXT, "r");
+ if (fp == NULL) {
+ fprintf(
+ stderr,
+ ENPREFIX "couldn't open the manpage from %s\n",
+ DIR_MANPAGE_TXT
+ );
+ return ConfigStatcodeNoManpage;
+ }
+
+ while ((ch = fgetc(fp)) != EOF) {
+ fputc(ch, stdout);
+ }
+
+ if (fclose(fp) == EOF) {
+ return ConfigStatcodeErr;
+ }
+
+ return ConfigStatcodeNormalExit;
+}
diff --git a/ud/c/config.h b/ud/c/config.h
new file mode 100644
index 0000000..b60b8b2
--- /dev/null
+++ b/ud/c/config.h
@@ -0,0 +1,35 @@
+#ifndef CONFIG_H
+#define CONFIG_H
+
+#include
+
+#include "array.adt.h"
+
+typedef enum {
+ LangoptEnumEn,
+ LangoptEnumKo
+} langopt_enum_t;
+
+typedef struct config {
+ array_t *srcs;
+ bool help;
+ array_t *msgs;
+ size_t iter;
+ langopt_enum_t lang;
+} config_t;
+
+typedef enum {
+ ConfigStatcodeOk,
+ ConfigStatcodeNormalExit,
+ ConfigStatcodeErr,
+ ConfigStatcodeNoMsg,
+ ConfigStatcodeNoManpage
+} config_statcode_t;
+
+void init_config(config_t *cfg);
+void destroy_config(config_t *cfg);
+config_statcode_t apply_config(config_t *cfg);
+
+extern config_t cfg;
+
+#endif
diff --git a/ud/c/config.internals.h b/ud/c/config.internals.h
new file mode 100644
index 0000000..6f7a6c5
--- /dev/null
+++ b/ud/c/config.internals.h
@@ -0,0 +1,22 @@
+#ifndef CONFIG_INTERNALS_H
+#define CONFIG_INTERNALS_H
+
+#include
+#include
+
+#include "global.h"
+#include "config.h"
+#include "wrappers.h"
+
+static config_statcode_t check_help(config_t *cfg);
+static config_statcode_t check_msg(config_t *cfg);
+
+static config_statcode_t print_manpage(void);
+static config_statcode_t print_manual_fallback(void);
+
+/*==================*
+ | EXTERN VARIABLES |
+ *==================*/
+config_t cfg = {0}; /* Since config does not change, I made it external */
+
+#endif
diff --git a/ud/c/global.h b/ud/c/global.h
new file mode 100644
index 0000000..817a131
--- /dev/null
+++ b/ud/c/global.h
@@ -0,0 +1,20 @@
+#ifndef GLOBAL_H
+#define GLOBAL_H
+
+#ifdef PROGNAME
+#define EXECNAME PROGNAME
+#else
+#define EXECNAME "ud"
+#endif
+
+#define ENPREFIX EXECNAME ": "
+
+#ifdef DIR_DATA
+#define DIR_MANPAGE DIR_DATA "/man/man1/ud.1"
+#else
+#define DIR_MANPAGE "/usr/local/share/man/man1/ud.1"
+#endif
+
+#define DIR_MANPAGE_TXT DIR_MANPAGE ".txt"
+
+#endif
diff --git a/ud/c/ud.c b/ud/c/ud.c
new file mode 100644
index 0000000..e7bb4cc
--- /dev/null
+++ b/ud/c/ud.c
@@ -0,0 +1,89 @@
+#include "ud.internals.h"
+
+int main(int argc, const char *argv[]) {
+ int main_ret = EXIT_SUCCESS;
+ config_statcode_t cfg_ret;
+
+ checkif_argc1(argc, optmds, optmds_len);
+
+ init_config(&cfg);
+ parse_argv(argc, argv, optmds, optmds_len, cfg.srcs);
+
+ cfg_ret = apply_config(&cfg);
+ switch (cfg_ret) {
+ case ConfigStatcodeOk: {
+ break;
+ }
+
+ case ConfigStatcodeNormalExit: {
+ goto cleanup;
+ }
+
+ case ConfigStatcodeErr:
+ [[fallthrough]];
+
+ case ConfigStatcodeNoMsg:
+ [[fallthrough]];
+
+ case ConfigStatcodeNoManpage: {
+ main_ret = EXIT_FAILURE;
+ goto cleanup;
+ }
+
+ default:
+ ; /* control never reaches default */
+ }
+
+ seed();
+ work(&cfg);
+
+cleanup:
+ destroy_config(&cfg);
+
+ return main_ret;
+}
+
+static void checkif_argc1(
+ int argc,
+ optmd_t table[],
+ size_t table_len
+) {
+ static const char *intro =
+ "NAME\n"
+ "\t" Cbwhite EXECNAME Creset
+ " - a fake updater\n"
+ "\n"
+ "USAGE\n"
+ "\t" EXECNAME " (-m | --message) string\n"
+ "\t" EXECNAME " (-h | --help)";
+
+ if (argc == 1) {
+ usage(table, table_len, intro);
+ exit(EXIT_SUCCESS); /* early exit */
+ }
+}
+
+static void work(config_t *cfg) {
+ size_t pidx, sidx, midx;
+ size_t msgs_len = array_size(cfg->msgs);
+
+ for (size_t i = 0; i < cfg->iter; i++) {
+ pidx = rand_range(0, prefix_len - 1);
+ sidx = rand_range(0, suffix_len - 1);
+ midx = rand_range(0, msgs_len - 1);
+ printf(
+ "%s: %s.%s\n",
+ get_prefix(cfg->lang, pidx),
+ (char *) array_peek(cfg->msgs, midx),
+ suffix[sidx]
+ );
+ }
+}
+
+static const char *get_prefix(langopt_enum_t lang, size_t idx) {
+ switch (lang) {
+ case LangoptEnumEn: return prefix[idx].en;
+ case LangoptEnumKo: return prefix[idx].ko;
+ default: return "unknown lang"; /* control never reaches default */
+ }
+}
diff --git a/ud/c/ud.internals.h b/ud/c/ud.internals.h
new file mode 100644
index 0000000..52253bd
--- /dev/null
+++ b/ud/c/ud.internals.h
@@ -0,0 +1,61 @@
+#ifndef UD_INTERNALS_H
+#define UD_INTERNALS_H
+
+#include
+#include
+
+#include "config.h"
+#include "global.h"
+#include "common.h"
+#include "colorcodes.h"
+#include "argp_drivers.h"
+
+typedef struct {
+ const char *en;
+ const char *ko;
+} prefix_t;
+
+static void checkif_argc1(
+ int argc,
+ optmd_t table[],
+ size_t table_len
+);
+
+static void work(config_t *cfg);
+static const char *get_prefix(langopt_enum_t lang, size_t idx);
+
+static const prefix_t prefix[] = {
+ {
+ .en = "download",
+ .ko = "다운로드"
+ },
+ {
+ .en = "archive",
+ .ko = "압축"
+ },
+ {
+ .en = "build",
+ .ko = "빌드"
+ },
+ {
+ .en = "extract",
+ .ko = "압축해제"
+ },
+ {
+ .en = "remove",
+ .ko = "삭제"
+ }
+};
+static const size_t prefix_len = ARRLEN(prefix);
+
+static const char *suffix[] = {
+ "dll",
+ "exe",
+ "zip",
+ "obj",
+ "txt",
+ "jpeg"
+ };
+static const size_t suffix_len = ARRLEN(suffix);
+
+#endif
diff --git a/ud/ud.1 b/ud/ud.1
new file mode 100644
index 0000000..49d9b57
--- /dev/null
+++ b/ud/ud.1
@@ -0,0 +1,20 @@
+.TH ud 1 2026-10-26 "Utils Project" "UTILS PROJECT"
+.SH NAME
+ud - a fake updater
+.SH SYNOPSIS
+.nf
+\fBud\fR (\-\fIm\fR | \-\-\fImessage\fB) string
+\fBud\fR (\-\fIh\fB | \-\-\fIhelp\fB)
+.fi
+.SH DESCRIPTION
+.SH USAGE
+.SH EXAMPLE
+.SH COMMAND LINE ARGUMENTS
+.SH NOTES
+.SH EXIT STATUS
+.SH IMPLEMENTATIONS
+.SH CONTRIBUTORS
+Visit \fIhttps://github.com/sdbx/Utils/graphs/contributors\fR to see all the contributors!
+.SH UTILS PROJECT
+\fBud\fR is part of the \fBUtils\fR utility suite.
+For more information, please visit \fIhttps://github.com/sdbx/Utils\fR to browse the whole project.
diff --git a/vars.mk b/vars.mk
index 29cc875..5af90cb 100644
--- a/vars.mk
+++ b/vars.mk
@@ -19,8 +19,9 @@ NSY := nsy
NSY2 := nsy2
YANDERE := yandere
IW := iw
+UD := ud
-TARGETS = $(BTN) $(HD) $(HHSS) $(NSY) $(NSY2) $(YANDERE) $(IW)
+TARGETS = $(BTN) $(HD) $(HHSS) $(NSY) $(NSY2) $(YANDERE) $(IW) $(UD)
TEST := test