AmendHub

Download

jcs

/

amend

/

rsrc.c

 

(View History)

jcs   *: Add native resource file handling Latest amendment: 178 on 2026-09-28

1 /*
2 * Copyright (c) 2026 joshua stein <jcs@jcs.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <ctype.h>
18 #include <stdio.h>
19 #include <string.h>
20
21 #include "rsrc.h"
22 #include "util.h"
23
24 /* column after the leading tab where the ascii comment starts */
25 #define RSRC_COMMENT_COL 54
26 #define RSRC_BYTES_PER_LINE 16
27 #define RSRC_BUF_GROW 4096
28
29 struct rsrc_buf {
30 char *data;
31 size_t size;
32 size_t len;
33 };
34
35 struct rsrc_attr {
36 char *name;
37 short flag;
38 } rsrc_attrs[] = {
39 { "sysheap", resSysHeap },
40 { "purgeable", resPurgeable },
41 { "locked", resLocked },
42 { "protected", resProtected },
43 { "preload", resPreload },
44 };
45
46 enum {
47 RSRC_PARSE_HEADER,
48 RSRC_PARSE_DATA
49 };
50
51 static const char rsrc_hex[] = "0123456789ABCDEF";
52
53 bool rsrc_buf_append(struct rsrc_buf *buf, const void *data, size_t len);
54 bool rsrc_derez_resource(struct rsrc_buf *out, Handle h, OSType type,
55 short id, Str255 name, short attrs);
56 size_t rsrc_escape(unsigned char *src, size_t len, char quote, char *dst,
57 size_t dstsize);
58 short rsrc_unescape(char **strp, char *end, char quote, unsigned char *dst,
59 size_t dstsize, size_t *dstlen);
60 short rsrc_hexval(char c);
61 char * rsrc_skip_ws(char *p, char *end);
62 short rsrc_parse_header(char *line, char *end, OSType *type, short *id,
63 Str255 name, short *attrs);
64 short rsrc_parse_hex(char *line, char *end, struct rsrc_buf *data);
65 short rsrc_add(OSType type, short id, Str255 name, short attrs,
66 struct rsrc_buf *data);
67
68 bool
69 rsrc_is_rsrc(const char *filename)
70 {
71 size_t len, slen;
72
73 len = strlen(filename);
74 if (len <= 5) /* .rsrc */
75 return false;
76
77 return (strcasecmp(filename + len - 5, ".rsrc") == 0);
78 }
79
80 short
81 rsrc_derez(Str255 path, char **ret, size_t *retlen)
82 {
83 struct rsrc_buf out = { 0 };
84 Handle h;
85 Str255 name;
86 OSType type;
87 short old_res, res, error = 0, ntypes, nres, t, r, id, attrs;
88 bool already_open = false;
89
90 *ret = NULL;
91 *retlen = 0;
92
93 old_res = CurResFile();
94
95 res = OpenRFPerm(path, 0, fsRdPerm);
96 if (res == -1) {
97 error = ResError();
98 if (error != eofErr)
99 return error;
100
101 /* no resource fork at all, treat it as empty */
102 error = 0;
103 goto derez_done;
104 }
105
106 if (res == old_res)
107 already_open = true;
108
109 UseResFile(res);
110 SetResLoad(false);
111
112 ntypes = Count1Types();
113 for (t = ntypes; t >= 1; t--) {
114 Get1IndType(&type, t);
115 nres = Count1Resources(type);
116 for (r = 1; r <= nres; r++) {
117 h = Get1IndResource(type, r);
118 if (h == NULL) {
119 error = ResError();
120 goto derez_bail;
121 }
122 LoadResource(h);
123 if ((error = ResError()) != noErr)
124 goto derez_bail;
125
126 GetResInfo(h, &id, &type, name);
127 attrs = GetResAttrs(h);
128
129 if (!rsrc_derez_resource(&out, h, type, id, name, attrs)) {
130 if (!already_open)
131 ReleaseResource(h);
132 error = -1;
133 goto derez_bail;
134 }
135 if (!already_open)
136 ReleaseResource(h);
137 }
138 }
139
140 derez_bail:
141 SetResLoad(true);
142 if (!already_open)
143 CloseResFile(res);
144 UseResFile(old_res);
145
146 if (error) {
147 if (out.data != NULL)
148 xfree(&out.data);
149 return error;
150 }
151
152 derez_done:
153 /* bile can't store empty objects */
154 if (out.len == 0 && !rsrc_buf_append(&out, "/* no resources */\r", 19))
155 return -1;
156
157 *ret = out.data;
158 *retlen = out.len;
159
160 return 0;
161 }
162
163 bool
164 rsrc_derez_resource(struct rsrc_buf *out, Handle h, OSType type, short id,
165 Str255 name, short attrs)
166 {
167 char line[128], esc[1024];
168 unsigned char *data, c;
169 size_t size, off, n, pos, len;
170 char hstate;
171 bool star, printable;
172
173 /* data 'TYPE' (id, "name", attrs) { */
174 rsrc_escape((unsigned char *)&type, sizeof(type), '\'', esc,
175 sizeof(esc));
176 len = snprintf(line, sizeof(line), "data '%s' (%d", esc, id);
177 if (!rsrc_buf_append(out, line, len))
178 return false;
179
180 if (name[0]) {
181 len = rsrc_escape(name + 1, name[0], '"', esc, sizeof(esc));
182 if (!rsrc_buf_append(out, ", \"", 3) ||
183 !rsrc_buf_append(out, esc, len) ||
184 !rsrc_buf_append(out, "\"", 1))
185 return false;
186 }
187
188 for (n = 0; n < nitems(rsrc_attrs); n++) {
189 if (!(attrs & rsrc_attrs[n].flag))
190 continue;
191 len = snprintf(line, sizeof(line), ", %s", rsrc_attrs[n].name);
192 if (!rsrc_buf_append(out, line, len))
193 return false;
194 }
195
196 if (!rsrc_buf_append(out, ") {\r", 4))
197 return false;
198
199 size = GetHandleSize(h);
200 hstate = HGetState(h);
201 HLock(h);
202 data = (unsigned char *)*h;
203
204 for (off = 0; off < size; off += RSRC_BYTES_PER_LINE) {
205 pos = 0;
206 line[pos++] = '\t';
207 line[pos++] = '$';
208 line[pos++] = '"';
209 for (n = 0; n < RSRC_BYTES_PER_LINE && off + n < size; n++) {
210 if (n > 0 && (n % 2) == 0)
211 line[pos++] = ' ';
212 c = data[off + n];
213 line[pos++] = rsrc_hex[c >> 4];
214 line[pos++] = rsrc_hex[c & 0xf];
215 }
216 line[pos++] = '"';
217
218 /* pad out and show the bytes as text in a comment like DeRez */
219 while (pos < RSRC_COMMENT_COL + 1)
220 line[pos++] = ' ';
221 line[pos++] = '/';
222 line[pos++] = '*';
223 line[pos++] = ' ';
224 star = false;
225 for (n = 0; n < RSRC_BYTES_PER_LINE && off + n < size; n++) {
226 c = data[off + n];
227 printable = (c >= ' ' && c != 0x7f && c <= 0xd8);
228
229 // avoid printing */ in the ascii dump
230 if (c == '*')
231 star = true;
232 else if (c == '/') {
233 if (star)
234 c = '.';
235 star = false;
236 } else if (printable)
237 star = false;
238
239 if (c == '\t')
240 c = '∆'; /* 0xc6 */
241 else if (c == '\r')
242 c = '¬'; /* 0xc2 */
243 else if (!printable)
244 c = '.';
245 line[pos++] = c;
246 }
247 line[pos++] = ' ';
248 line[pos++] = '*';
249 line[pos++] = '/';
250 line[pos++] = '\r';
251
252 if (!rsrc_buf_append(out, line, pos)) {
253 HUnlock(h);
254 return false;
255 }
256 }
257
258 HSetState(h, hstate);
259
260 if (!rsrc_buf_append(out, "};\r\r", 4))
261 return false;
262
263 return true;
264 }
265
266 short
267 rsrc_rez(Str255 path, const char *text, size_t len)
268 {
269 struct rsrc_buf data = { 0 };
270 Str255 name;
271 OSType type;
272 char *line, *end, *eol;
273 char err[128];
274 unsigned long linenum = 0;
275 short old_res, res, error = 0, state, id, attrs, ret;
276
277 err[0] = '\0';
278 old_res = CurResFile();
279
280 CreateResFile(path);
281 error = ResError();
282 if (error != noErr && error != dupFNErr)
283 return error;
284
285 res = OpenRFPerm(path, 0, fsRdWrPerm);
286 if (res == -1)
287 return ResError();
288
289 UseResFile(res);
290
291 state = RSRC_PARSE_HEADER;
292 end = (char *)text + len;
293 for (line = (char *)text; line < end; line = eol + 1) {
294 linenum++;
295 for (eol = line; eol < end && *eol != '\r' && *eol != '\n'; eol++)
296 ;
297
298 /* skip blank lines and comments */
299 line = rsrc_skip_ws(line, eol);
300 if (line == eol ||
301 (eol - line > 1 && line[0] == '/' && line[1] == '*'))
302 continue;
303
304 switch (state) {
305 case RSRC_PARSE_HEADER:
306 if (rsrc_parse_header(line, eol, &type, &id, name,
307 &attrs) != 0) {
308 snprintf(err, sizeof(err), "Malformed resource header on "
309 "line %lu", linenum);
310 goto rez_bail;
311 }
312 data.len = 0;
313 state = RSRC_PARSE_DATA;
314 break;
315 case RSRC_PARSE_DATA:
316 if (line[0] == '}') {
317 ret = rsrc_add(type, id, name, attrs, &data);
318 if (ret == -1) {
319 snprintf(err, sizeof(err), "Duplicate resource %s %d "
320 "on line %lu", OSTypeToString(type), id, linenum);
321 goto rez_bail;
322 }
323 if (ret != noErr) {
324 snprintf(err, sizeof(err), "Failed adding resource %s "
325 "%d on line %lu: %d", OSTypeToString(type), id,
326 linenum, ret);
327 goto rez_bail;
328 }
329 state = RSRC_PARSE_HEADER;
330 break;
331 }
332 if (rsrc_parse_hex(line, eol, &data) != 0) {
333 snprintf(err, sizeof(err), "Malformed resource data on "
334 "line %lu", linenum);
335 goto rez_bail;
336 }
337 break;
338 }
339 }
340
341 if (state != RSRC_PARSE_HEADER)
342 snprintf(err, sizeof(err), "Unexpected end of resource data on "
343 "line %lu", linenum);
344
345 rez_bail:
346 if (data.data != NULL)
347 xfree(&data.data);
348
349 CloseResFile(res);
350 UseResFile(old_res);
351
352 if (err[0]) {
353 warn("%s", err);
354 return -1;
355 }
356
357 return 0;
358 }
359
360 short
361 rsrc_parse_header(char *line, char *end, OSType *type, short *id,
362 Str255 name, short *attrs)
363 {
364 unsigned char tmp[256];
365 char word[32];
366 size_t tmplen, n;
367 long val = 0;
368 bool neg = false;
369
370 /* data 'LDEF' (129, "file list", purgeable) { */
371
372 if (end - line < 4 || strncmp(line, "data", 4) != 0)
373 return -1;
374 line = rsrc_skip_ws(line + 4, end);
375
376 /* 'TYPE' */
377 if (line >= end || *line != '\'')
378 return -1;
379 line++;
380 if (rsrc_unescape(&line, end, '\'', tmp, sizeof(tmp), &tmplen) != 0 ||
381 tmplen != sizeof(OSType))
382 return -1;
383 memcpy(type, tmp, sizeof(OSType));
384
385 /* (id */
386 line = rsrc_skip_ws(line, end);
387 if (line >= end || *line != '(')
388 return -1;
389 line = rsrc_skip_ws(line + 1, end);
390 if (line < end && *line == '-') {
391 neg = true;
392 line++;
393 }
394 if (line >= end || !isdigit((unsigned char)*line))
395 return -1;
396 for (; line < end && isdigit((unsigned char)*line); line++)
397 val = (val * 10) + (*line - '0');
398 *id = (short)(neg ? -val : val);
399
400 /* then any of , "name" and , attribute until ) */
401 name[0] = 0;
402 *attrs = 0;
403 for (;;) {
404 line = rsrc_skip_ws(line, end);
405 if (line >= end)
406 return -1;
407 if (*line == ')') {
408 line++;
409 break;
410 }
411 if (*line != ',')
412 return -1;
413 line = rsrc_skip_ws(line + 1, end);
414 if (line >= end)
415 return -1;
416
417 if (*line == '"') {
418 line++;
419 if (rsrc_unescape(&line, end, '"', tmp, sizeof(tmp),
420 &tmplen) != 0)
421 return -1;
422 name[0] = tmplen;
423 memcpy(name + 1, tmp, tmplen);
424 continue;
425 }
426
427 for (n = 0; line < end && n < sizeof(word) - 1 &&
428 isalpha((unsigned char)*line); n++, line++)
429 word[n] = *line;
430 word[n] = '\0';
431 if (n == 0)
432 return -1;
433
434 for (n = 0; n < nitems(rsrc_attrs); n++) {
435 if (strcasecmp(word, rsrc_attrs[n].name) == 0) {
436 *attrs |= rsrc_attrs[n].flag;
437 break;
438 }
439 }
440 if (n == nitems(rsrc_attrs))
441 return -1;
442 }
443
444 /* { */
445 line = rsrc_skip_ws(line, end);
446 if (line >= end || *line != '{')
447 return -1;
448
449 return 0;
450 }
451
452 short
453 rsrc_parse_hex(char *line, char *end, struct rsrc_buf *data)
454 {
455 unsigned char c;
456 short hi, lo;
457
458 if (end - line < 2 || line[0] != '$' || line[1] != '"')
459 return -1;
460 line += 2;
461
462 for (;;) {
463 line = rsrc_skip_ws(line, end);
464 if (line >= end)
465 /* no closing quote */
466 return -1;
467 if (*line == '"')
468 break;
469 if (end - line < 2)
470 return -1;
471 if ((hi = rsrc_hexval(line[0])) == -1 ||
472 (lo = rsrc_hexval(line[1])) == -1)
473 return -1;
474 c = (hi << 4) | lo;
475 if (!rsrc_buf_append(data, &c, 1))
476 return -1;
477 line += 2;
478 }
479
480 /* anything after the closing quote is the ascii comment */
481 return 0;
482 }
483
484 short
485 rsrc_add(OSType type, short id, Str255 name, short attrs,
486 struct rsrc_buf *data)
487 {
488 Handle h;
489 short error;
490
491 /* AddResource doesn't check for duplicates */
492 SetResLoad(false);
493 h = Get1Resource(type, id);
494 SetResLoad(true);
495 if (h != NULL)
496 return -1;
497
498 h = NewHandle(data->len);
499 if (h == NULL)
500 return memFullErr;
501 if (data->len) {
502 HLock(h);
503 memcpy(*h, data->data, data->len);
504 HUnlock(h);
505 }
506
507 AddResource(h, type, id, name);
508 if ((error = ResError()) != noErr) {
509 DisposHandle(h);
510 return error;
511 }
512
513 WriteResource(h);
514 if ((error = ResError()) != noErr)
515 return error;
516
517 if (attrs) {
518 SetResAttrs(h, attrs);
519 if ((error = ResError()) != noErr)
520 return error;
521 }
522
523 ReleaseResource(h);
524
525 return 0;
526 }
527
528 bool
529 rsrc_buf_append(struct rsrc_buf *buf, const void *data, size_t len)
530 {
531 EXPAND_TO_FIT(buf->data, buf->size, buf->len, len, RSRC_BUF_GROW);
532 if (buf->data == NULL)
533 return false;
534
535 memcpy(buf->data + buf->len, data, len);
536 buf->len += len;
537
538 return true;
539 }
540
541 /* escape quote, backslash, and non-printables the way DeRez does */
542 size_t
543 rsrc_escape(unsigned char *src, size_t len, char quote, char *dst,
544 size_t dstsize)
545 {
546 size_t n, pos = 0;
547 unsigned char c;
548
549 for (n = 0; n < len && pos + 5 < dstsize; n++) {
550 c = src[n];
551 if (c == quote || c == '\\') {
552 dst[pos++] = '\\';
553 dst[pos++] = c;
554 } else if (c < ' ' || c == 0x7f) {
555 dst[pos++] = '\\';
556 dst[pos++] = '0';
557 dst[pos++] = 'x';
558 dst[pos++] = rsrc_hex[c >> 4];
559 dst[pos++] = rsrc_hex[c & 0xf];
560 } else
561 dst[pos++] = c;
562 }
563 dst[pos] = '\0';
564
565 return pos;
566 }
567
568 /* read up to the closing quote, leaving *strp after it */
569 short
570 rsrc_unescape(char **strp, char *end, char quote, unsigned char *dst,
571 size_t dstsize, size_t *dstlen)
572 {
573 char *p = *strp;
574 size_t len = 0;
575 short hi, lo;
576
577 while (p < end && *p != quote) {
578 if (len + 1 >= dstsize)
579 return -1;
580
581 if (*p != '\\') {
582 dst[len++] = *p++;
583 continue;
584 }
585
586 if (++p >= end)
587 return -1;
588
589 if (*p == '0' && end - p >= 4 && (p[1] == 'x' || p[1] == 'X')) {
590 if ((hi = rsrc_hexval(p[2]) == -1) ||
591 (lo = rsrc_hexval(p[3]) == -1))
592 return -1;
593 dst[len++] = (hi << 4) | lo;
594 p += 4;
595 } else if (*p == '\\' || *p == '\'' || *p == '"')
596 dst[len++] = *p++;
597 else
598 return -1;
599 }
600
601 if (p >= end)
602 /* no closing quote */
603 return -1;
604
605 *strp = p + 1;
606 *dstlen = len;
607
608 return 0;
609 }
610
611 short
612 rsrc_hexval(char c)
613 {
614 if (c >= '0' && c <= '9')
615 return c - '0';
616 if (c >= 'A' && c <= 'F')
617 return c - 'A' + 10;
618 if (c >= 'a' && c <= 'f')
619 return c - 'a' + 10;
620
621 return -1;
622 }
623
624 char *
625 rsrc_skip_ws(char *p, char *end)
626 {
627 while (p < end && (*p == ' ' || *p == '\t'))
628 p++;
629
630 return p;
631 }