Update TODO list for more recent thinking.
[sgt/tweak] / main.c
CommitLineData
6e182d98 1/*
32c65619 2 * Potential future TODO items. Points marked ISSUE need to be
3 * resolved one way or another, with good justification for the
4 * decision made, before implementation begins.
6e182d98 5 *
6e182d98 6 * - Multiple buffers, multiple on-screen windows.
7 * + ^X^F to open new file
8 * + ^X^R to open new file RO
9 * + ^X b to switch buffers in a window
10 * + ^X o to switch windows
11 * + ^X 2 to split a window
12 * + ^X 1 to destroy all windows but this
13 * + ^X 0 to destroy this window
14 * + ^X ^ to enlarge this window by one line
15 * + width settings vary per buffer (aha, _that's_ why I wanted
16 * a buffer structure surrounding the raw B-tree)
17 * + hex-editor-style minibuffer for entering search terms,
18 * rather than the current rather crap one; in particular
19 * this enables pasting into the search string.
32c65619 20 * + ISSUE: how exactly do we deal with the problem of saving
21 * over a file which we're maintaining references to in
22 * another buffer? The _current_ buffer can at least be
23 * sorted out by replacing it with a fresh tree containing a
24 * single file-data block, but other buffers are in trouble.
25 * * if we can rely on Unix fd semantics, one option is just
26 * to keep the fd open on the original file, and then the
27 * data stays around even after we rename(2) our new
28 * version over the top. Disk space usage gets silly after
29 * a few iterations, but it's better than nothing.
6e182d98 30 *
31 * - Undo!
32 * + this actually doesn't seem _too_ horrid. For a start, one
33 * simple approach would be to clone the entire buffer B-tree
34 * every time we perform an operation! That's actually not
35 * _too_ expensive, if we maintain a limit on the number of
36 * operations we may undo.
37 * + I had also thought of cloning the tree we insert for each
38 * buf_insert_data and cloning the one removed for each
39 * buf_delete_data (both must be cloned for an overwrite),
40 * but I'm not convinced that simply cloning the entire thing
41 * isn't a superior option.
d28a4799 42 * + this really starts to show up the distinction between a
43 * `buffer' and a bare tree. A buffer is something which has
44 * an undo chain attached; so, in particular, the cut buffer
45 * shouldn't be one. Sort that out.
6e182d98 46 *
6e182d98 47 * - In-place editing.
32c65619 48 * + this is an extra option useful for editing disk devices
49 * directly (!), or other situation in which it's impossible
50 * or impractical to rename(2) your new file over the old
51 * one. It causes a change of semantics when saving: instead
52 * of constructing a new backup file and writing it over the
53 * old one, we simply seek within the original file and write
54 * out all the pieces that have changed.
55 * + Saving the file involves identifying the bits of the file
56 * that need to change, and changing them. A piece of file
57 * can be discarded as `no change required' if it's
58 * represented in the buffer by a from-file block whose file
59 * offset is equal to its offset in the buffer.
60 * * Once we have identified all the bits that do need to
61 * change, we have to draw up a dependency graph to
62 * indicate which bits want to be copied from which other
63 * bits. (You don't want to overwrite a piece of file if
64 * you still have from-file blocks pointing at that
65 * piece.) This is a directed graph with nodes
66 * corresponding to intervals of the file, and edges
67 * indicating that the source node's interval is intended
68 * to end up containing the data from the target node's
69 * interval in the original file. Another node type is
70 * `literal data', which can be the target of an edge but
71 * never the source.
72 * - note that this means any two nodes connected by an
73 * edge must represent intervals of the same length.
74 * Sometimes this means that an interval must be split
75 * into pieces even though it is represented in the
76 * buffer by a single large from-file block (if
77 * from-file blocks copying _from_ it don't cover the
78 * whole of it). I suspect the simplest approach here
79 * is just to start by making a B-tree of division
80 * points in the file: every from-file block adds four
81 * division points (for start and end of both source
82 * and dest interval), and once the tree is complete,
83 * each graph node represents the interval between two
84 * adjacent division points.
85 * - ISSUE: actually, that strategy is inadequate:
86 * consider a large from-file block displaced by only
87 * one byte from its source location. The above
88 * strategy gives division points at x, x+1, x+y,
89 * x+y+1, but the interval [x,x+1] actually wants to
90 * point to [x+1,x+2] and we don't have a division
91 * point for that. Worse still, finding a way to add
92 * the remaining division points is also undesirable
93 * because there'd be so many of them. Needs design
94 * changes.
95 * * Then, any node which is not the target of any edge
96 * represents a piece of file which it's safe to write
97 * over, so we do so and throw away the node.
98 * * If we run out of such nodes and the graph is still
99 * non-empty, it's because all remaining nodes are part of
100 * loops. A loop must represent a set of disjoint
101 * intervals in the file, all the same length, which need
102 * to be permuted cyclically. So we deal with such a loop
103 * by reading a chunk of data from the start of one of the
104 * intervals and holding it, then copying from the next
105 * interval to that one, and so on until we've gone round
106 * the loop.
107 * + the intervals in the loop might be far too big to
108 * hold an entire interval's worth of real data in
109 * memory, so we might have to do it piecewise.
110 * + ISSUE: I wonder if a warning of some sort might be in
111 * order for if you accidentally request most of the file be
112 * moved about. This sort of trickery is really intended for
113 * small changes to a large file; if you (say) enable insert
114 * mode while editing a hard disk and accidentally leave
115 * everything one byte further up, you _really_ don't want to
116 * hit Save. The semantics of the warning are difficult,
117 * though.
6e182d98 118 */
119
11825bd4 120#include "tweak.h"
121
6e182d98 122#include <stdio.h>
123#include <stdlib.h>
124#include <string.h>
125#include <ctype.h>
126#include <assert.h>
127
128#if defined(unix) && !defined(GO32)
129#include <signal.h>
130#include <sys/ioctl.h>
131#include <termios.h>
132#elif defined(MSDOS)
133#include <dos.h>
134#include <process.h>
135#endif
136
6e182d98 137static void init(void);
138static void done(void);
139static void load_file (char *);
140
141char toprint[256]; /* LUT: printable versions of chars */
142char hex[256][3]; /* LUT: binary to hex, 1 byte */
143
144char message[80];
145
11825bd4 146char decstatus[] = "%s TWEAK "VER": %-18.18s %s posn=%-10"OFF"d size=%-10"OFF"d";
147char hexstatus[] = "%s TWEAK "VER": %-18.18s %s posn=0x%-8"OFF"X size=0x%-8"OFF"X";
6e182d98 148char *statfmt = hexstatus;
149
150char last_char;
151char *pname;
152char *filename = NULL;
153buffer *filedata, *cutbuffer = NULL;
154int fix_mode = FALSE;
155int look_mode = FALSE;
156int eager_mode = FALSE;
157int insert_mode = FALSE;
158int edit_type = 1; /* 1,2 are hex digits, 0=ascii */
159int finished = FALSE;
160int marking = FALSE;
161int modified = FALSE;
162int new_file = FALSE; /* shouldn't need initialisation -
163 * but let's not take chances :-) */
11825bd4 164fileoffset_t width = 16;
165fileoffset_t realoffset = 0, offset = 16;
6e182d98 166
167int ascii_enabled = TRUE;
168
11825bd4 169fileoffset_t file_size = 0, top_pos = 0, cur_pos = 0, mark_point = 0;
6e182d98 170
171int scrlines;
172
173/*
174 * Main program
175 */
176int main(int argc, char **argv) {
11825bd4 177 fileoffset_t newoffset = -1, newwidth = -1;
6e182d98 178
179 /*
180 * Parse command line arguments
181 */
182 pname = *argv; /* program name */
183 if (argc < 2) {
184 fprintf(stderr,
185 "usage: %s [-f] [-l] [-e] filename\n"
186 " or %s -D to write default tweak.rc to stdout\n",
187 pname, pname);
188 return 0;
189 }
190
191 while (--argc > 0) {
192 char c, *p = *++argv, *value;
193
194 if (*p == '-') {
195 p++;
196 while (*p) switch (c = *p++) {
197 case 'o': case 'O':
198 case 'w': case 'W':
199 /*
200 * these parameters require arguments
201 */
202 if (*p)
203 value = p, p = "";
204 else if (--argc)
205 value = *++argv;
206 else {
207 fprintf(stderr, "%s: option `-%c' requires an argument\n",
208 pname, c);
209 return 1;
210 }
211 switch (c) {
212 case 'o': case 'O':
11825bd4 213 newoffset = parse_num(value, NULL);
6e182d98 214 break;
215 case 'w': case 'W':
11825bd4 216 newwidth = parse_num(value, NULL);
6e182d98 217 break;
218 }
219 break;
220 case 'f': case 'F':
221 fix_mode = TRUE;
222 break;
223 case 'l': case 'L':
224 look_mode = TRUE;
225 break;
226 case 'e': case 'E':
227 eager_mode = TRUE;
228 break;
229 case 'D':
230 write_default_rc();
231 return 0;
232 break;
233 }
234 } else {
235 if (filename) {
236 fprintf(stderr, "%s: multiple filenames specified\n", pname);
237 return 1;
238 }
239 filename = p;
240 }
241 }
242
243 if (!filename) {
244 fprintf(stderr, "%s: no filename specified\n", pname);
245 return 1;
246 }
247
248 read_rc();
249 if (newoffset != -1)
250 realoffset = newoffset;
251 if (newwidth != -1)
252 width = newwidth;
253 load_file (filename);
254 init();
255 fix_offset();
256 do {
257 draw_scr ();
258 proc_key ();
259 } while (!finished);
260 done();
261
262 return 0;
263}
264
265/*
266 * Fix up `offset' to match `realoffset'. Also, while we're here,
267 * enable or disable ASCII mode and sanity-check the width.
268 */
269void fix_offset(void) {
270 if (3*width+11 > display_cols) {
271 width = (display_cols-11) / 3;
11825bd4 272 sprintf (message, "Width reduced to %"OFF"d to fit on the screen", width);
6e182d98 273 }
274 if (4*width+14 > display_cols) {
275 ascii_enabled = FALSE;
276 if (edit_type == 0)
277 edit_type = 1; /* force to hex mode */
278 } else
279 ascii_enabled = TRUE;
280 offset = realoffset % width;
281 if (!offset)
282 offset = width;
283}
284
285/*
286 * Initialise stuff at the beginning of the program: mostly the
287 * display.
288 */
289static void init(void) {
290 int i;
291
292 display_setup();
293
ef7de295 294 display_define_colour(COL_BUFFER, -1, -1, FALSE);
295 display_define_colour(COL_SELECT, 0, 7, TRUE);
296 display_define_colour(COL_STATUS, 11, 4, TRUE);
297 display_define_colour(COL_ESCAPE, 9, 0, FALSE);
298 display_define_colour(COL_INVALID, 11, 0, FALSE);
6e182d98 299
300 for (i=0; i<256; i++) {
301 sprintf(hex[i], "%02X", i);
302 toprint[i] = (i>=32 && i<127 ? i : '.');
303 }
304}
305
306/*
307 * Clean up all the stuff that init() did.
308 */
309static void done(void) {
310 display_cleanup();
311}
312
313/*
314 * Load the file specified on the command line.
315 */
316static void load_file (char *fname) {
317 FILE *fp;
318
319 file_size = 0;
320 if ( (fp = fopen (fname, "rb")) ) {
321 if (eager_mode) {
11825bd4 322 size_t len;
6e182d98 323 static char buffer[4096];
324
325 filedata = buf_new_empty();
326
327 file_size = 0;
328
329 /*
330 * We've opened the file. Load it.
331 */
332 while ( (len = fread (buffer, 1, sizeof(buffer), fp)) > 0 ) {
333 buf_insert_data (filedata, buffer, len, file_size);
334 file_size += len;
335 }
336 fclose (fp);
337 assert(file_size == buf_length(filedata));
11825bd4 338 sprintf(message, "loaded %s (size %"OFF"d == 0x%"OFF"X).",
6e182d98 339 fname, file_size, file_size);
340 } else {
341 filedata = buf_new_from_file(fp);
342 file_size = buf_length(filedata);
11825bd4 343 sprintf(message, "opened %s (size %"OFF"d == 0x%"OFF"X).",
6e182d98 344 fname, file_size, file_size);
345 }
346 new_file = FALSE;
347 } else {
348 if (look_mode || fix_mode) {
349 fprintf(stderr, "%s: file %s not found, and %s mode active\n",
350 pname, fname, (look_mode ? "LOOK" : "FIX"));
351 exit (1);
352 }
353 filedata = buf_new_empty();
354 sprintf(message, "New file %s.", fname);
355 new_file = TRUE;
356 }
357}
358
359/*
360 * Save the file. Return TRUE on success, FALSE on error.
361 */
362int save_file (void) {
363 FILE *fp;
11825bd4 364 fileoffset_t pos = 0;
6e182d98 365
366 if (look_mode)
367 return FALSE; /* do nothing! */
368
369 if ( (fp = fopen (filename, "wb")) ) {
370 static char buffer[SAVE_BLKSIZ];
371
372 while (pos < file_size) {
11825bd4 373 fileoffset_t size = file_size - pos;
6e182d98 374 if (size > SAVE_BLKSIZ)
375 size = SAVE_BLKSIZ;
376
377 buf_fetch_data (filedata, buffer, size, pos);
378 if (size != fwrite (buffer, 1, size, fp)) {
379 fclose (fp);
380 return FALSE;
381 }
382 pos += size;
383 }
384 } else
385 return FALSE;
386 fclose (fp);
387 return TRUE;
388}
389
390/*
391 * Make a backup of the file, if such has not already been done.
392 * Return TRUE on success, FALSE on error.
393 */
394int backup_file (void) {
395 char backup_name[FILENAME_MAX];
396
397 if (new_file)
398 return TRUE; /* unnecessary - pretend it's done */
399 strcpy (backup_name, filename);
400#if defined(unix) && !defined(GO32)
401 strcat (backup_name, ".bak");
402#elif defined(MSDOS)
403 {
404 char *p, *q;
405
406 q = NULL;
407 for (p = backup_name; *p; p++) {
408 if (*p == '\\')
409 q = NULL;
410 else if (*p == '.')
411 q = p;
412 }
413 if (!q)
414 q = p;
415 strcpy (q, ".BAK");
416 }
417#endif
418 remove (backup_name); /* don't care if this fails */
419 return !rename (filename, backup_name);
420}
421
422static unsigned char *scrbuf = NULL;
423static int scrbuflines = 0;
424
425/*
426 * Draw the screen, for normal usage.
427 */
428void draw_scr (void) {
429 int scrsize, scroff, llen, i, j;
11825bd4 430 fileoffset_t currpos;
431 fileoffset_t marktop, markbot;
432 int mark;
6e182d98 433 char *p;
434 unsigned char c, *q;
435 char *linebuf;
436
437 scrlines = display_rows - 2;
438 if (scrlines > scrbuflines) {
439 scrbuf = (scrbuf ?
440 realloc(scrbuf, scrlines*width) :
441 malloc(scrlines*width));
442 if (!scrbuf) {
443 done();
444 fprintf(stderr, "%s: out of memory!\n", pname);
445 exit (2);
446 }
447 scrbuflines = scrlines;
448 }
449
450 linebuf = malloc(width*4+20);
451 if (!linebuf) {
452 done();
453 fprintf(stderr, "%s: out of memory!\n", pname);
454 exit (2);
455 }
456 memset (linebuf, ' ', width*4+13);
457 linebuf[width*4+13] = '\0';
458
459 if (top_pos == 0)
460 scroff = width - offset;
461 else
462 scroff = 0;
463 scrsize = scrlines * width - scroff;
464 if (scrsize > file_size - top_pos)
465 scrsize = file_size - top_pos;
466
467 buf_fetch_data (filedata, scrbuf, scrsize, top_pos);
468
469 scrsize += scroff; /* hack but it'll work */
470
471 mark = marking && (cur_pos != mark_point);
472 if (mark) {
473 if (cur_pos > mark_point)
474 marktop = mark_point, markbot = cur_pos;
475 else
476 marktop = cur_pos, markbot = mark_point;
477 } else
478 marktop = markbot = 0; /* placate gcc */
479
480 currpos = top_pos;
481 q = scrbuf;
482
483 for (i=0; i<scrlines; i++) {
484 display_moveto (i, 0);
485 if (currpos<=cur_pos || currpos<file_size) {
486 p = hex[(currpos >> 24) & 0xFF];
487 linebuf[0]=p[0];
488 linebuf[1]=p[1];
489 p = hex[(currpos >> 16) & 0xFF];
490 linebuf[2]=p[0];
491 linebuf[3]=p[1];
492 p = hex[(currpos >> 8) & 0xFF];
493 linebuf[4]=p[0];
494 linebuf[5]=p[1];
495 p = hex[currpos & 0xFF];
496 linebuf[6]=p[0];
497 linebuf[7]=p[1];
498 for (j=0; j<width; j++) {
499 if (scrsize > 0) {
500 if (currpos == 0 && j < width-offset)
501 p = " ", c = ' ';
502 else
503 p = hex[*q], c = *q++;
504 scrsize--;
505 } else {
506 p = " ", c = ' ';
507 }
508 linebuf[11+3*j]=p[0];
509 linebuf[12+3*j]=p[1];
510 linebuf[13+3*width+j]=toprint[c];
511 }
512 llen = (currpos ? width : offset);
513 if (mark && currpos<markbot && currpos+llen>marktop) {
514 /*
515 * Some of this line is marked. Maybe all. Whatever
516 * the precise details, there will be two regions
517 * requiring highlighting: a hex bit and an ascii
518 * bit.
519 */
11825bd4 520 fileoffset_t localstart= (currpos<marktop ? marktop :
521 currpos) - currpos;
522 fileoffset_t localstop = (currpos+llen>markbot ? markbot :
523 currpos+llen) - currpos;
6e182d98 524 localstart += width-llen;
525 localstop += width-llen;
526 display_write_chars(linebuf, 11+3*localstart);
527 display_set_colour(COL_SELECT);
528 display_write_chars(linebuf+11+3*localstart,
529 3*(localstop-localstart)-1);
530 display_set_colour(COL_BUFFER);
531 if (ascii_enabled) {
532 display_write_chars(linebuf+10+3*localstop,
533 3+3*width+localstart-3*localstop);
534 display_set_colour(COL_SELECT);
535 display_write_chars(linebuf+13+3*width+localstart,
536 localstop-localstart);
537 display_set_colour(COL_BUFFER);
538 display_write_chars(linebuf+13+3*width+localstop,
539 width-localstop);
540 } else {
541 display_write_chars(linebuf+10+3*localstop,
542 2+3*width-3*localstop);
543 }
1610a3c6 544 } else {
545 display_set_colour(COL_BUFFER);
6e182d98 546 display_write_chars(linebuf,
547 ascii_enabled ? 13+4*width : 10+3*width);
1610a3c6 548 }
6e182d98 549 }
550 currpos += (currpos ? width : offset);
551 display_clear_to_eol();
552 }
553
554 {
555 char status[80];
556 int slen;
557 display_moveto (display_rows-2, 0);
558 display_set_colour(COL_STATUS);
559 sprintf(status, statfmt,
560 (modified ? "**" : " "),
561 filename,
562 (insert_mode ? "(Insert)" :
563 look_mode ? "(LOOK) " :
564 fix_mode ? "(FIX) " : "(Ovrwrt)"),
565 cur_pos, file_size);
566 slen = strlen(status);
567 if (slen > display_cols)
568 slen = display_cols;
569 display_write_chars(status, slen);
570 while (slen++ < display_cols)
571 display_write_str(" ");
572 display_set_colour(COL_BUFFER);
573 }
574
575 display_moveto (display_rows-1, 0);
576 display_write_str (message);
577 display_clear_to_eol();
578 message[0] = '\0';
579
580 i = cur_pos - top_pos;
581 if (top_pos == 0)
582 i += width - offset;
583 j = (edit_type ? (i%width)*3+10+edit_type : (i%width)+13+3*width);
584 if (j >= display_cols)
585 j = display_cols-1;
586 free (linebuf);
587 display_moveto (i/width, j);
588 display_refresh ();
589}
590
ef7de295 591volatile int safe_update, update_required;
592void update (void);
593
6e182d98 594/*
595 * Get a string, in the "minibuffer". Return TRUE on success, FALSE
596 * on break. Possibly syntax-highlight the entered string for
597 * backslash-escapes, depending on the "highlight" parameter.
598 */
599int get_str (char *prompt, char *buf, int highlight) {
600 int maxlen = 79 - strlen(prompt); /* limit to 80 - who cares? :) */
601 int len = 0;
602 int c;
603
604 for (EVER) {
605 display_moveto (display_rows-1, 0);
606 display_set_colour (COL_MINIBUF);
607 display_write_str (prompt);
608 if (highlight) {
609 char *q, *p = buf, *r = buf+len;
610 while (p<r) {
611 q = p;
612 if (*p == '\\') {
613 p++;
614 if (p<r && *p == '\\')
615 p++, display_set_colour(COL_ESCAPE);
40d827aa 616 else if (p>=r || !isxdigit ((unsigned char)*p))
6e182d98 617 display_set_colour(COL_INVALID);
40d827aa 618 else if (p+1>=r || !isxdigit ((unsigned char)p[1]))
6e182d98 619 p++, display_set_colour(COL_INVALID);
620 else
621 p+=2, display_set_colour(COL_ESCAPE);
622 } else {
623 while (p<r && *p != '\\')
624 p++;
625 display_set_colour (COL_MINIBUF);
626 }
627 display_write_chars (q, p-q);
628 }
629 } else
630 display_write_chars (buf, len);
631 display_set_colour (COL_MINIBUF);
632 display_clear_to_eol();
633 display_refresh();
634 if (update_required)
635 update();
636 safe_update = TRUE;
637 c = display_getkey();
638 safe_update = FALSE;
639 if (c == 13 || c == 10) {
640 buf[len] = '\0';
641 return TRUE;
642 } else if (c == 27 || c == 7) {
643 display_beep();
644 display_post_error();
645 strcpy (message, "User Break!");
646 return FALSE;
647 }
648
649 if (c >= 32 && c <= 126) {
650 if (len < maxlen)
651 buf[len++] = c;
652 else
653 display_beep();
654 }
655
656 if ((c == 127 || c == 8) && len > 0)
657 len--;
658
659 if (c == 'U'-'@') /* ^U kill line */
660 len = 0;
661 }
662}
663
664/*
665 * Take a buffer containing possible backslash-escapes, and return
666 * a buffer containing a (binary!) string. Since the string is
667 * binary, it cannot be null terminated: hence the length is
668 * returned from the function. The string is processed in place.
669 *
670 * Escapes are simple: a backslash followed by two hex digits
671 * represents that character; a doubled backslash represents a
672 * backslash itself; a backslash followed by anything else is
673 * invalid. (-1 is returned if an invalid sequence is detected.)
674 */
675int parse_quoted (char *buffer) {
676 char *p, *q;
677
678 p = q = buffer;
679 while (*p) {
680 while (*p && *p != '\\')
681 *q++ = *p++;
682 if (*p == '\\') {
683 p++;
684 if (*p == '\\')
685 *q++ = *p++;
40d827aa 686 else if (p[1] && isxdigit((unsigned char)*p) &&
687 isxdigit((unsigned char)p[1])) {
6e182d98 688 char buf[3];
689 buf[0] = *p++;
690 buf[1] = *p++;
691 buf[2] = '\0';
692 *q++ = strtol(buf, NULL, 16);
693 } else
694 return -1;
695 }
696 }
697 return q - buffer;
698}
699
700/*
701 * Suspend program. (Or shell out, depending on OS, of course.)
702 */
703void suspend(void) {
704#if defined(unix) && !defined(GO32)
705 done();
706 raise (SIGTSTP);
707 init();
708#elif defined(MSDOS)
709 done();
710 spawnl (P_WAIT, getenv("COMSPEC"), "", NULL);
711 init();
712#else
713 display_beep();
714 strcpy(message, "Suspend function not yet implemented.");
715#endif
716}
717
6e182d98 718void update (void) {
719 display_recheck_size();
720 fix_offset ();
721 draw_scr ();
722}
723
724void schedule_update(void) {
725 if (safe_update)
726 update();
727 else
728 update_required = TRUE;
729}
730
11825bd4 731fileoffset_t parse_num (char *buffer, int *error) {
6e182d98 732 if (error)
733 *error = FALSE;
734 if (!buffer[strspn(buffer, "0123456789")]) {
735 /* interpret as decimal */
11825bd4 736 return ATOOFF(buffer);
6e182d98 737 } else if (buffer[0]=='0' && (buffer[1]=='X' || buffer[1]=='x') &&
738 !buffer[2+strspn(buffer+2,"0123456789ABCDEFabcdef")]) {
11825bd4 739 return STRTOOFF(buffer+2, NULL, 16);
6e182d98 740 } else if (buffer[0]=='$' &&
741 !buffer[1+strspn(buffer+1,"0123456789ABCDEFabcdef")]) {
11825bd4 742 return STRTOOFF(buffer+1, NULL, 16);
6e182d98 743 } else {
744 return 0;
745 if (error)
746 *error = TRUE;
747 }
748}