ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
zalgo.c
Go to the documentation of this file.
1#include <defer.h>
4#include <ascii-chat/common.h>
5#include <stdint.h>
6#include <string.h>
7
9static uint32_t zalgo_hash(uint32_t value) {
10 value ^= value >> 16;
11 value = (uint32_t)((uint64_t)value * UINT64_C(0x7feb352d));
12 value ^= value >> 15;
13 value = (uint32_t)((uint64_t)value * UINT64_C(0x846ca68b));
14 return value ^ (value >> 16);
15}
16
17static const unsigned zalgo_maximum[] = {1, 1, 1, 1, 2, 3, 4, 5, 7, 10};
18
19unsigned zalgo_cell_marks(uint32_t row, uint32_t col, int level, uint16_t marks[ZALGO_MAX_MARKS]) {
20 if (level < 1 || level > 10 || !marks)
21 return 0;
22 static const unsigned coverage[] = {3, 6, 10, 18, 28, 40, 55, 70, 85, 100};
23 static const unsigned minimum[] = {1, 1, 1, 1, 1, 1, 2, 3, 4, 6};
24 static const uint16_t short_marks[] = {0x0334, 0x0335, 0x0336, 0x0337, 0x0338};
25 static const uint16_t stack_marks[] = {0x0300, 0x0301, 0x0302, 0x0303, 0x0308, 0x030a,
26 0x0310, 0x0311, 0x0324, 0x0325, 0x0329, 0x0330};
27 unsigned index = (unsigned)level - 1;
28 uint32_t hash = zalgo_hash((uint32_t)((uint64_t)row * UINT64_C(0x9e3779b9)) ^ col ^ UINT32_C(0x5a17));
29 if (hash % 100 >= coverage[index])
30 return 0;
31 unsigned count = minimum[index] + ((hash >> 8) % 256) * (zalgo_maximum[index] - minimum[index] + 1) / 256;
32 for (unsigned mark = 0; mark < count; mark++) {
33 uint32_t choice = zalgo_hash((uint32_t)((uint64_t)hash + mark));
34 marks[mark] = mark == 0 ? short_marks[choice % 5] : stack_marks[choice % 12];
35 }
36 return count;
37}
38
40static size_t zalgo_escape_length(const char *s, size_t n) {
41 if (n < 2)
42 return n;
43 size_t i = 2;
44 if (s[1] == '[') {
45 while (i < n) {
46 unsigned char c = (unsigned char)s[i++];
47 if (c >= 0x40 && c <= 0x7e)
48 break;
49 }
50 } else if (s[1] == ']' || s[1] == 'P' || s[1] == '_' || s[1] == '^' || s[1] == 'X') {
51 while (i < n) {
52 if ((s[1] == ']' && s[i] == '\a') || (s[i] == '\x1b' && i + 1 < n && s[i + 1] == '\\')) {
53 i += s[i] == '\a' ? 1 : 2;
54 break;
55 }
56 i++;
57 }
58 } else {
59 i = 1;
60 while (i < n && s[i] >= 0x20 && s[i] <= 0x2f)
61 i++;
62 if (i < n)
63 i++;
64 }
65 return i;
66}
67
69asciichat_error_t zalgo_apply(const char *input, size_t length, int level, size_t max_output_bytes, char **output) {
70 if (!output)
71 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing Zalgo output pointer");
72 *output = NULL;
73 if (!input || level < 1 || level > 10)
74 return SET_ERRNO(ERROR_INVALID_PARAM, "Zalgo requires a frame and intensity 1-10");
75
76 // Bound REP expansion before allocating: a short remote frame can contain a
77 // very large repeat count. Four bytes per repeated code point is conservative.
78 const size_t expansion_limit = 1024 * 1024;
79 size_t expansion_bound = length;
80 bool has_rep = false;
81 if (length > expansion_limit)
82 return SET_ERRNO(ERROR_INVALID_PARAM, "Zalgo frame exceeds expansion limit");
83 for (size_t i = 0; i + 1 < length; i++) {
84 if (input[i] != '\x1b' || input[i + 1] != '[')
85 continue;
86 size_t j = i + 2;
87 uint32_t parameter = 0;
88 while (j < length && ((input[j] >= '0' && input[j] <= '9') || input[j] == ';')) {
89 if (input[j] == ';') {
90 parameter = 0;
91 } else {
92 unsigned digit = (unsigned)(input[j] - '0');
93 if (parameter > (UINT32_MAX - digit) / 10)
94 return SET_ERRNO(ERROR_INVALID_PARAM, "Zalgo frame contains an overflowing ANSI parameter");
95 parameter = parameter * 10 + digit;
96 }
97 j++;
98 }
99 if (j < length && input[j] == 'b') {
100 has_rep = true;
101 if (parameter > (expansion_limit - expansion_bound) / 4)
102 return SET_ERRNO(ERROR_INVALID_PARAM, "Zalgo REP expansion exceeds frame limit");
103 expansion_bound += (size_t)parameter * 4;
104 }
105 i = j;
106 }
107
108 char *expanded = NULL;
109 defer(SAFE_FREE(expanded));
110 if (has_rep) {
111 expanded = ansi_expand_rle(input, length);
112 } else {
113 expanded = SAFE_MALLOC(length + 1, char *);
114 memcpy(expanded, input, length);
115 expanded[length] = '\0';
116 }
117 if (!expanded)
118 return SET_ERRNO(ERROR_INVALID_PARAM, "Cannot expand empty Zalgo frame");
119 size_t n = strlen(expanded);
120 if (n > max_output_bytes) {
121 return SET_ERRNO(ERROR_INVALID_PARAM, "Zalgo frame exceeds output limit");
122 }
123 size_t mark_budget = max_output_bytes - n;
124 size_t factor = 1 + 2 * zalgo_maximum[level - 1];
125 // Every source byte introduces at most the selected level's mark limit.
126 if (n > (SIZE_MAX - 1) / factor) {
127 return SET_ERRNO(ERROR_INVALID_PARAM, "Zalgo frame is too large");
128 }
129 size_t capacity = n * factor < max_output_bytes ? n * factor : max_output_bytes;
130 SAFE_MALLOC_DEFER_FREE(result, capacity + 1, char *);
131 char *dst = result;
132 uint32_t row = 0, col = 0;
133 for (size_t i = 0; i < n;) {
134 unsigned char c = (unsigned char)expanded[i];
135 if (c == 0x1b) {
136 size_t count = zalgo_escape_length(expanded + i, n - i);
137 memcpy(dst, expanded + i, count);
138 dst += count;
139 i += count;
140 continue;
141 }
142 *dst++ = expanded[i++];
143 if (c == '\n') {
144 row++;
145 col = 0;
146 } else if (c == '\r') {
147 col = 0;
148 } else if (c == '\t') {
149 col = (col / 8 + 1) * 8;
150 } else if (c >= 0x20 && c < 0x7f) {
152 unsigned count = c == ' ' ? 0 : zalgo_cell_marks(row, col, level, marks);
153 col++;
154 if (count * 2 > mark_budget) {
155 return SET_ERRNO(ERROR_INVALID_PARAM, "Zalgo decoration exceeds output limit");
156 }
157 mark_budget -= count * 2;
158 for (unsigned mark = 0; mark < count; mark++) {
159 uint16_t cp = marks[mark];
160 *dst++ = (char)(0xc0 | (cp >> 6));
161 *dst++ = (char)(0x80 | (cp & 0x3f));
162 }
163 } else if (c >= 0xc0) {
164 // Preserve multibyte characters; continuation bytes do not advance columns.
165 col++;
166 }
167 }
168 *dst = '\0';
169 *output = result;
170 result = NULL;
171 return ASCIICHAT_OK;
172}
#define SET_ERRNO(code,...)
⚙️ Common definitions, error codes, macros, and types shared throughout the application
unsigned short uint16_t
Definition common.h:61
unsigned int uint32_t
Definition common.h:62
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_MALLOC_DEFER_FREE(name, size, cast)
Definition common.h:386
#define SAFE_MALLOC(size, cast)
Definition common.h:265
unsigned long long uint64_t
Definition common.h:63
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
char * ansi_expand_rle(const char *input, size_t input_len)
Expand RLE escape sequences in a string.
Definition rle.c:14
static bool size_t index
Definition queries.h:8
🔤 String Manipulation and Shell Escaping Utilities
asciichat_error_t zalgo_apply(const char *input, size_t length, int level, size_t max_output_bytes, char **output)
Definition zalgo.c:69
unsigned zalgo_cell_marks(uint32_t row, uint32_t col, int level, uint16_t marks[ZALGO_MAX_MARKS])
Definition zalgo.c:19
#define ZALGO_MAX_MARKS
Definition zalgo.h:4