Skip to content
Snippets Groups Projects
term.c 115 KiB
Newer Older
deuce's avatar
deuce committed
				xmodem_cancel(&xm);
deuce's avatar
deuce committed
			}
Deucе's avatar
Deucе committed
			file_bytes_left -= wr;
deuce's avatar
deuce committed
			block_num++;
		}
Deucе's avatar
Deucе committed
                /* Use correct file size */
deuce's avatar
deuce committed
		fflush(fp);
Deucе's avatar
Deucе committed
		lprintf(LOG_DEBUG, "file_bytes=%u", file_bytes);
		lprintf(LOG_DEBUG, "file_bytes_left=%u", file_bytes_left);
		lprintf(LOG_DEBUG, "filelength=%u", filelength(fileno(fp)));
Deucе's avatar
Deucе committed
		if (file_bytes < (ulong)filelength(fileno(fp))) {
			lprintf(LOG_INFO, "Truncating file to %lu bytes", (ulong)file_bytes);
			chsize(fileno(fp), (ulong)file_bytes); /* 4GB limit! */
		}
		else {
deuce's avatar
deuce committed
			file_bytes = filelength(fileno(fp));
Deucе's avatar
Deucе committed
		}
deuce's avatar
deuce committed
		fclose(fp);
deuce's avatar
deuce committed
		fp = NULL;
Deucе's avatar
Deucе committed
		t = time(NULL) - startfile;
		if (!t)
			t = 1;
		if (success)
			lprintf(LOG_INFO, "Successful - Time: %lu:%02lu  CPS: %lu",
			    (ulong)(t / 60), (ulong)(t % 60), (ulong)(file_bytes / t));
deuce's avatar
deuce committed
		else
Deucе's avatar
Deucе committed
			lprintf(LOG_ERR, "File Transfer %s", xm.cancelled ? "Cancelled" : "Failure");
deuce's avatar
deuce committed

Deucе's avatar
Deucе committed
		if (!(mode & XMODEM) && ftime)
			setfdate(str, ftime);
deuce's avatar
deuce committed

Deucе's avatar
Deucе committed
		if (!success && (file_bytes == 0)) { /* remove 0-byte files */
deuce's avatar
deuce committed
			if (remove(str) == -1)
				lprintf(LOG_ERR, "Unable to remove empty file %s\n", str);
		}
Deucе's avatar
Deucе committed
		if (mode & XMODEM) /* maximum of one file */
deuce's avatar
deuce committed
			break;
Deucе's avatar
Deucе committed
		if ((cps = (unsigned)(file_bytes / t)) == 0)
			cps = 1;
Deucе's avatar
Deucе committed
		if (--total_files <= 0)
			extra_pass = true;
Deucе's avatar
Deucе committed
		total_bytes -= file_bytes;
		if ((total_files > 1) && total_bytes) {
			lprintf(LOG_INFO, "Remaining - Time: %lu:%02lu  Files: %u  KBytes: %" PRId64,
			    (total_bytes / cps) / 60,
			    (total_bytes / cps) % 60,
			    total_files,
			    total_bytes / 1024);
Deucе's avatar
Deucе committed
		}
Deucе's avatar
Deucе committed
	if (fp)
deuce's avatar
deuce committed
		fclose(fp);
	transfer_complete(success, was_binary);
/* End of X/Y-MODEM stuff */
Deucе's avatar
Deucе committed
void
music_control(struct bbslist *bbs)
deuce's avatar
deuce committed
{
	struct  text_info     txtinfo;
	struct ciolib_screen *savscrn;
	int                   i;
Deucе's avatar
Deucе committed
	gettextinfo(&txtinfo);
	savscrn = savescreen();
	setfont(0, false, 1);
	setfont(0, false, 2);
	setfont(0, false, 3);
	setfont(0, false, 4);
	init_uifc(false, false);
Deucе's avatar
Deucе committed
	i = cterm->music_enable;
	uifc.helpbuf = music_helpbuf;
	if (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &i, NULL, "ANSI Music Setup", music_names) != -1)
		cterm->music_enable = i;
		check_exit(false);
deuce's avatar
deuce committed
	uifcbail();
	restorescreen(savscrn);
	freescreen(savscrn);
deuce's avatar
deuce committed
}
Deucе's avatar
Deucе committed
void
font_control(struct bbslist *bbs, struct cterminal *cterm)
	struct ciolib_screen *savscrn;
	struct  text_info     txtinfo;
	int                   i, j, k;
Deucе's avatar
Deucе committed
	if (safe_mode)
Deucе's avatar
Deucе committed
	gettextinfo(&txtinfo);
	savscrn = savescreen();
	setfont(0, false, 1);
	setfont(0, false, 2);
	setfont(0, false, 3);
	setfont(0, false, 4);
	init_uifc(false, false);
Deucе's avatar
Deucе committed
	switch (cio_api.mode) {
		case CIOLIB_MODE_CONIO:
		case CIOLIB_MODE_CONIO_FULLSCREEN:
deuce's avatar
deuce committed
		case CIOLIB_MODE_CURSES_ASCII:
		case CIOLIB_MODE_CURSES_IBM:
		case CIOLIB_MODE_ANSI:
			uifcmsg("Not supported in this video output mode.",
			    "Font cannot be changed in the current video output mode");
			check_exit(false);
Deucе's avatar
Deucе committed
			i = j = cterm->altfont[0];
			uifc.helpbuf = "`Font Setup`\n\n"
			    "Change the current font.  Font must support the current video mode:\n\n"
			    "`8x8`  Used for screen modes with 35 or more lines and all C64/C128 modes\n"
			    "`8x14` Used for screen modes with 28 and 34 lines\n"
			    "`8x16` Used for screen modes with 30 lines or fewer than 28 lines.";
			k = uifc.list(WIN_MID | WIN_SAV | WIN_INS, 0, 0, 0, &i, &j, "Font Setup", font_names);
			if (k != -1) {
				if (k & MSK_INS) {
					struct file_pick fpick;
Deucе's avatar
Deucе committed

					j = filepick(&uifc, "Load Font From File", &fpick, ".", NULL, 0);
					check_exit(false);
Deucе's avatar
Deucе committed
					if ((j != -1) && (fpick.files >= 1))
						loadfont(fpick.selected[0]);
					filepick_free(&fpick);
				}
Deucе's avatar
Deucе committed
					setfont(i, false, 1);
deuce's avatar
deuce committed
					cterm->altfont[0] = i;
Deucе's avatar
Deucе committed
			else {
				check_exit(false);
Deucе's avatar
Deucе committed
			}
			break;
	restorescreen(savscrn);
	freescreen(savscrn);
Deucе's avatar
Deucе committed
void
capture_control(struct bbslist *bbs)
	struct ciolib_screen *savscrn;
	char                 *cap;
	struct  text_info     txtinfo;
	int                   i, j;
Deucе's avatar
Deucе committed
	if (safe_mode)
Deucе's avatar
Deucе committed
	gettextinfo(&txtinfo);
	savscrn = savescreen();
	setfont(0, false, 1);
	setfont(0, false, 2);
	setfont(0, false, 3);
	setfont(0, false, 4);
	cap = (char *)alloca(cterm->height * cterm->width * 2);
Deucе's avatar
Deucе committed
	gettext(cterm->x, cterm->y, cterm->x + cterm->width - 1, cterm->y + cterm->height - 1, cap);
	init_uifc(false, false);
Deucе's avatar
Deucе committed
	if (!cterm->log) {
		struct file_pick fpick;
Deucе's avatar
Deucе committed
		char            *opts[] = {
			"ASCII",
			"Raw",
			"Binary",
			"Binary with SAUCE",
			""
Deucе's avatar
Deucе committed
		};

		i = 0;
		uifc.helpbuf = "~ Capture Type ~\n\n"
		    "`ASCII`              ASCII only (no ANSI escape sequences)\n"
		    "`Raw`                Preserves ANSI sequences\n"
		    "`Binary`             Saves current screen in IBM-CGA/BinaryText format\n"
		    "`Binary with SAUCE`  Saves current screen in BinaryText format with SAUCE\n"
		    "\n"
		    "Raw is useful for stealing ANSI screens from other systems.\n"
		    "Don't do that though.  :-)";
		if (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &i, NULL, "Capture Type", opts) != -1) {
			j = filepick(&uifc, "Capture File", &fpick, bbs->dldir, i >= 2 ? "*.bin" : NULL,
			        UIFC_FP_ALLOWENTRY | UIFC_FP_OVERPROMPT);
			check_exit(false);
Deucе's avatar
Deucе committed
			if ((j != -1) && (fpick.files >= 1)) {
				if (i >= 2) {
					FILE *fp = fopen(fpick.selected[0], "wb");
Deucе's avatar
Deucе committed

					if (fp == NULL) {
						sprintf(err, "Error %u opening file '%s'", errno, fpick.selected[0]);
						uifc.msg(err);
Deucе's avatar
Deucе committed
					}
					else {
						uifc.pop("Writing to file");
						fwrite(cap, sizeof(uint8_t), cterm->width * cterm->height * 2, fp);
Deucе's avatar
Deucе committed
						if (i > 2) {
							time_t       t = time(NULL);
							struct tm   *tm;
							struct sauce sauce;

							memset(&sauce, 0, sizeof(sauce));
							memcpy(sauce.id, SAUCE_ID, sizeof(sauce.id));
							memcpy(sauce.ver, SAUCE_VERSION, sizeof(sauce.ver));
							memset(sauce.title, ' ', sizeof(sauce.title));
							memset(sauce.author, ' ', sizeof(sauce.author));
							memset(sauce.group, ' ', sizeof(sauce.group));
Deucе's avatar
Deucе committed
							if (bbs != NULL) {
								memcpy(sauce.title, bbs->name,
								    MIN(strlen(bbs->name), sizeof(sauce.title)));
								memcpy(sauce.author, bbs->user,
								    MIN(strlen(bbs->user), sizeof(sauce.author)));
							if ((tm = localtime(&t)) != NULL) { // The null-terminator
                                                                                            // overwrites the first
                                                                                            // byte of filesize
								sprintf(sauce.date, "%04u%02u%02u",
								    1900 + tm->tm_year, 1 + tm->tm_mon, tm->tm_mday);
Deucе's avatar
Deucе committed
							sauce.filesize = LE_INT32(ftell(fp)); // LE
							sauce.datatype = sauce_datatype_bin;
							sauce.filetype = cterm->width / 2;
Deucе's avatar
Deucе committed
							if (ciolib_getvideoflags()
							    & (CIOLIB_VIDEO_BGBRIGHT | CIOLIB_VIDEO_NOBLINK))
								sauce.tflags |= sauce_ansiflag_nonblink;

							fputc(SAUCE_SEPARATOR, fp);
Deucе's avatar
Deucе committed

                                                        /* No comment block (no comments) */
							fwrite(&sauce.id, sizeof(sauce.id), 1, fp);
							fwrite(&sauce.ver, sizeof(sauce.ver), 1, fp);
							fwrite(&sauce.title, sizeof(sauce.title), 1, fp);
							fwrite(&sauce.author, sizeof(sauce.author), 1, fp);
							fwrite(&sauce.group, sizeof(sauce.group), 1, fp);
							fwrite(&sauce.date, sizeof(sauce.date), 1, fp);
							fwrite(&sauce.filesize, sizeof(sauce.filesize), 1, fp);
							fwrite(&sauce.datatype, sizeof(sauce.datatype), 1, fp);
							fwrite(&sauce.filetype, sizeof(sauce.filetype), 1, fp);
							fwrite(&sauce.tinfo1, sizeof(sauce.tinfo1), 1, fp);
							fwrite(&sauce.tinfo2, sizeof(sauce.tinfo2), 1, fp);
							fwrite(&sauce.tinfo3, sizeof(sauce.tinfo3), 1, fp);
							fwrite(&sauce.tinfo4, sizeof(sauce.tinfo4), 1, fp);
							fwrite(&sauce.comments, sizeof(sauce.comments), 1, fp);
							fwrite(&sauce.tflags, sizeof(sauce.tflags), 1, fp);
							fwrite(&sauce.tinfos, sizeof(sauce.tinfos), 1, fp);
						}
						fclose(fp);
						uifc.pop(NULL);
						sprintf(msg, "Screen saved to '%s'", getfname(fpick.selected[0]));
						uifc.msg(msg);
					}
Deucе's avatar
Deucе committed
				}
				else {
					cterm_openlog(cterm, fpick.selected[0], i ? CTERM_LOG_RAW : CTERM_LOG_ASCII);
				}
			filepick_free(&fpick);
		}
Deucе's avatar
Deucе committed
		else {
			check_exit(false);
Deucе's avatar
Deucе committed
		}
Deucе's avatar
Deucе committed
		if (cterm->log & CTERM_LOG_PAUSED) {
Deucе's avatar
Deucе committed
			};

			i = 0;
			uifc.helpbuf = "`Capture Control`\n\n"
			    "~ Unpause ~ Continues logging\n"
			    "~ Close ~   Closes the log\n\n";
			if (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &i, NULL, "Capture Control", opts) != -1) {
				switch (i) {
						check_exit(false);
Deucе's avatar
Deucе committed
						cterm->log = cterm->log & CTERM_LOG_MASK;
Deucе's avatar
Deucе committed
			};

			i = 0;
			uifc.helpbuf = "`Capture Control`\n\n"
			    "~ Pause ~ Suspends logging\n"
			    "~ Close ~ Closes the log\n\n";
			if (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &i, NULL, "Capture Control", opts) != -1) {
				switch (i) {
						check_exit(false);
rswindell's avatar
rswindell committed
						cterm->log |= CTERM_LOG_PAUSED;
	restorescreen(savscrn);
	freescreen(savscrn);
Deucе's avatar
Deucе committed
#define OUTBUF_SIZE 2048
Deucе's avatar
Deucе committed
#define WRITE_OUTBUF() \
	if (outbuf_size > 0) { \
		cterm_write(cterm, outbuf, outbuf_size, (char *)ansi_replybuf, sizeof(ansi_replybuf), &speed); \
Deucе's avatar
Deucе committed
		outbuf_size = 0; \
		if (ansi_replybuf[0]) \
		conn_send(ansi_replybuf, strlen((char *)ansi_replybuf), 0); \
Deucе's avatar
Deucе committed
		updated = true; \
Deucе's avatar
Deucе committed
int
get_cache_fn_base(struct bbslist *bbs, char *fn, size_t fnsz)
	get_syncterm_filename(fn, fnsz, SYNCTERM_PATH_CACHE, false);
	backslash(fn);
	strcat(fn, bbs->name);
	backslash(fn);
	if (!isdir(fn))
deuce's avatar
deuce committed
		mkpath(fn);
	if (!isdir(fn))
		return 0;
	return 1;
}
Deucе's avatar
Deucе committed
int
get_cache_fn_subdir(struct bbslist *bbs, char *fn, size_t fnsz, const char *subdir)
{
	int ret;

	ret = get_cache_fn_base(bbs, fn, fnsz);
	if (ret == 0)
		return ret;
	strcat(fn, subdir);
	backslash(fn);
	if (!isdir(fn))
		mkpath(fn);
	if (!isdir(fn))
		return 0;
	return 1;
}
Deucе's avatar
Deucе committed
static int
clean_path(char *fn, size_t fnsz)

	fp = _fullpath(NULL, fn, fnsz);
Deucе's avatar
Deucе committed
	if ((fp == NULL) || strcmp(fp, fn)) {
		FREE_AND_NULL(fp);
		return 0;
	}
	FREE_AND_NULL(fp);
	return 1;
}
// ============ This section taken from pnmgamma.c ============
/* pnmgamma.c - perform gamma correction on a PNM image
**
** Copyright (C) 1991 by Bill Davidson and Jef Poskanzer.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation.  This software is provided "as is" without express or
** implied warranty.
*/

static void
buildBt709ToSrgbGamma(const uint8_t maxval) {
    uint8_t const newMaxval = 255;
    double const gammaSrgb = 2.4;
/*----------------------------------------------------------------------------
   Build a gamma table of size maxval+1 for the combination of the
   inverse of ITU Rec BT.709 and the forward SRGB gamma transfer
   functions.  I.e. this converts from Rec 709 to SRGB.

   'gammaSrgb' must be 2.4 for true SRGB.
-----------------------------------------------------------------------------*/
    double const oneOverGamma709  = 0.45;
    double const gamma709         = 1.0 / oneOverGamma709;
    double const oneOverGammaSrgb = 1.0 / gammaSrgb;
    double const normalizer       = 1.0 / maxval;

	return;
    /* This transfer function is linear for sample values 0
       .. maxval*.018 and an exponential for larger sample values.
       The exponential is slightly stretched and translated, though,
       unlike the popular pure exponential gamma transfer function.
    */

    uint8_t const linearCutoff709 = (uint8_t) (maxval * 0.018 + 0.5);
    double const linearCompression709 = 
        0.018 / (1.099 * pow(0.018, oneOverGamma709) - 0.099);

    double const linearCutoffSrgb = 0.0031308;
    double const linearExpansionSrgb = 
        (1.055 * pow(0.0031308, oneOverGammaSrgb) - 0.055) / 0.0031308;

    int i;

    for (i = 0; i <= maxval; ++i) {
        double const normalized = i * normalizer;
            /* Xel sample value normalized to 0..1 */
        double radiance;
        double srgb;

        if (i < linearCutoff709 / linearCompression709)
            radiance = normalized * linearCompression709;
        else
            radiance = pow((normalized + 0.099) / 1.099, gamma709);

        assert(radiance <= 1.0);

        if (radiance < linearCutoffSrgb * normalizer)
            srgb = radiance * linearExpansionSrgb;
        else
            srgb = 1.055 * pow(normalized, oneOverGammaSrgb) - 0.055;

        assert(srgb <= 1.0);

        pnm_gamma[i] = srgb * newMaxval + 0.5;
    }
}


// ====================== End of section ======================

bool
is_pbm_whitespace(char c)
{
	switch(c) {
		case ' ':
		case '\t':
		case '\r':
		case '\n':
			return true;
	}
	return false;
}

bool
read_pbm_char(FILE *f, off_t *lastpos, char *ch)
{
	if (lastpos != NULL) {
		*lastpos = ftello(f);
		if (*lastpos == -1)
			return false;
	}
	if (fread(ch, 1, 1, f) != 1)
		return false;
	return true;
}

bool
skip_pbm_whitespace(FILE *f)
{
	char ch;
	off_t lastpos;
	bool start = true;

	for (;;) {
		if (!read_pbm_char(f, &lastpos, &ch)) {
			return false;
		}
		if (start) {
			if (!is_pbm_whitespace(ch)) {
				return false;
			}
			start = false;
		}
		if (ch == '#') {
			do {
				if (!read_pbm_char(f, &lastpos, &ch)) {
					return false;
				}
			} while (ch != '\r' && ch != '\n');
		}
		if (!is_pbm_whitespace(ch)) {
			if (fseeko(f, lastpos, SEEK_SET) != 0)
				return false;
			return true;
		}
	}
}

uintmax_t
read_pbm_number(FILE *f)
{
	char value[256]; // Should be big enough ;)
	char *endptr;
	int i;
	off_t lastpos;

	for (i = 0; i < sizeof(value) - 1; i++) {
		if (!read_pbm_char(f, &lastpos, &value[i]))
			break;
		if (value[i] < '0' || value[i] > '9') {
			if (i == 0)
				return UINTMAX_MAX;
			value[i] = 0;
			if (fseeko(f, lastpos, SEEK_SET) != 0)
				return UINTMAX_MAX;
			return strtoumax(value, &endptr, 10);
		}
	}
	return UINTMAX_MAX;
}

static bool
read_pbm_text_raster(struct ciolib_mask *ret, size_t sz, FILE *f)
{
	uintmax_t num;
	size_t    i;
	size_t    byte = 0;
	uint8_t   bit = 7;

	memset(ret->bits, 0, (sz + 7) / 8);
	for (i = 0; i < sz; i++) {
		num = read_pbm_number(f);
		if (num > 1)
			return false;
		ret->bits[byte] |= num << bit;
		if (bit == 0)
			bit = 7;
		else
			bit--;
	}
	return true;
}

static bool
read_ppm_any_raster(struct ciolib_pixels *p, size_t sz, uint8_t max, FILE *f, uintmax_t(*readnum)(FILE *))
{
	uintmax_t num;
	size_t    i;
	uint32_t  pdata;

	for (i = 0; i < sz; i++) {
		pdata = 0x80000000;	// RGB value (anything less is palette)

		// Red
		num = readnum(f);
		if (num > 255)
			return false;
		pdata |= (pnm_gamma[num] << 16);

		// Green
		num = readnum(f);
		if (num > 255)
			return false;
		pdata |= (pnm_gamma[num] << 8);

		// Blue
		num = readnum(f);
		if (num > 255)
			return false;
		pdata |= (pnm_gamma[num] << 0);
		p->pixels[i] = pdata;
	}
	return true;
}

static bool
read_ppm_text_raster(struct ciolib_pixels *p, size_t sz, uint8_t max, FILE *f)
{
	return read_ppm_any_raster(p, sz, max, f, read_pbm_number);
}

static uintmax_t
read_pbm_byte(FILE *f)
{
	uint8_t b;

	if (fread(&b, 1, 1, f) != 1)
		return UINTMAX_MAX;
	return b;
}

static bool
read_ppm_raw_raster(struct ciolib_pixels *p, size_t sz, uint8_t max, FILE *f)
{
	return read_ppm_any_raster(p, sz, max, f, read_pbm_byte);
}

static struct ciolib_pixels *
alloc_ciolib_pixels(uint32_t w, uint32_t h)
{
	struct ciolib_pixels *ret;
	size_t pszo;
	size_t psz;

	pszo = w * h;
	if (h != 0 && pszo / h != w)
		return NULL;
	psz = pszo * sizeof(uint32_t);
	if (psz / sizeof(uint32_t) != pszo)
		return NULL;
	ret = malloc(sizeof(*ret));
	if (ret == NULL)
		return ret;
	ret->width = w;
	ret->height = h;
	ret->pixelsb = NULL;
	if (psz > 0) {
		ret->pixels = malloc(psz);
		if (ret->pixels == NULL) {
			free(ret);
			return NULL;
		}
	}
	else {
		ret->pixels = NULL;
	}
	return ret;
}

static struct ciolib_mask *
alloc_ciolib_mask(uint32_t w, uint32_t h)
{
	struct ciolib_mask *ret;
	size_t psz;

	psz = w * h;
	if (h != 0 && psz / h != w)
		return NULL;
	ret = malloc(sizeof(*ret));
	if (ret == NULL)
		return ret;
	ret->width = w;
	ret->height = h;
	if (psz > 0) {
		ret->bits = malloc((psz + 7) / 8);
		if (ret->bits == NULL) {
			free(ret);
			return NULL;
		}
	}
	else {
		ret->bits = NULL;
	}
	return ret;
}

static void *
read_pbm(const char *fn, bool bitmap)
{
	uintmax_t             width;
	uintmax_t             height;
	uintmax_t             maxval;
	uintmax_t             overflow;
	FILE                 *f = fopen(fn, "rb");
	struct ciolib_mask   *mret = NULL;
	struct ciolib_pixels *pret = NULL;
	size_t                raster_size;
	size_t                raster_bit_size;
	char                  magic[2];
	bool                  b;

	if (f == NULL)
		goto fail;
	if (fread(magic, sizeof(magic), 1, f) != 1)
		goto fail;
	if (magic[0] != 'P')
		goto fail;
	switch (magic[1]) {
		case '1':
		case '4':
			if (!bitmap)
				goto fail;
			break;
		case '3':
		case '6':
			if (bitmap)
				goto fail;
			break;
		default:
			goto fail;
	}

	if (!skip_pbm_whitespace(f))
		goto fail;

	assert(UINTMAX_MAX > UINT32_MAX);
	width = read_pbm_number(f);
	if (width > UINT32_MAX)
		goto fail;

	if (!skip_pbm_whitespace(f))
		goto fail;

	height = read_pbm_number(f);
	if (height > UINT32_MAX)
		goto fail;

	// Check for multiplcation overflow
	overflow = width * height;
	if (width != 0 && overflow / height != width)
		goto fail;
	// Check for type truncation
	raster_size = overflow;
	if (raster_size != overflow)
		goto fail;

	if (magic[1] == '3' || magic[1] == '6') {
		if (!skip_pbm_whitespace(f))
			goto fail;

		maxval = read_pbm_number(f);
		if (maxval == UINTMAX_MAX)
			goto fail;

		if (maxval > 255)
			goto fail;
	}

	if (!skip_pbm_whitespace(f))
		goto fail;

	switch (magic[1]) {
		case '1':
		case '4':
			raster_bit_size = (raster_size + 7) / 8;
			mret = alloc_ciolib_mask(width, height);
			if (mret == NULL)
				goto fail;
			if (magic[1] == '1')
				b = read_pbm_text_raster(mret, raster_size, f);
			else
				b = fread(mret->bits, raster_bit_size, 1, f) == 1;
			if (!b)
				goto fail;
			fclose(f);
			return mret;
		case '3':
		case '6':
			pret = alloc_ciolib_pixels(width, height);
			if (pret == NULL)
				goto fail;
			if (magic[1] == '3')
				b = read_ppm_text_raster(pret, raster_size, maxval, f);
			else
				b = read_ppm_raw_raster(pret, raster_size, maxval, f);
			if (!b)
				goto fail;
			fclose(f);
			return pret;
		default:
			goto fail;
	}

fail:
	freemask(mret);
	freepixels(pret);
	if (f)
		fclose(f);
	return NULL;
}

static void *
b64_decode_alloc(const char *strbuf, size_t slen, size_t *outlen)
{
	void  *ret;
	int    ol;
	size_t sz;

	sz = slen * 3 + 3 / 4 + 1;
	ret = malloc(sz);
	if (!ret)
		return NULL;
	ol = b64_decode(ret, sz, strbuf, slen);
	if (ol == -1) {
		free(ret);
		return NULL;
	}
	if (outlen != NULL)
		*outlen = ol;
	return ret;
}

static void
draw_ppm_str_handler(char *str, size_t slen, char *fn, void *apcd)
{
	struct ciolib_mask   *ctmask = NULL;
	char                 *p;
	char                 *p2;
	void                 *mask = NULL;
	char                 *maskfn = NULL;
	char                 *ppmfn = NULL;
	struct ciolib_pixels *ppmp = NULL;
	unsigned long        *val;
	unsigned long         sx = 0; // Source X to start at
	unsigned long         sy = 0; // Source Y to start at
	unsigned long         sw = 0; // Source width to show
	unsigned long         sh = 0; // Source height to show
	unsigned long         dx = 0; // Destination X to start at
	unsigned long         dy = 0; // Destination Y to start at
	unsigned long         mx = 0; // Mask X to start at
	unsigned long         my = 0; // Mask Y to start at
	unsigned long         mw = 0; // Width of the mask
	unsigned long         mh = 0; // Height of the mask
	size_t                mlen = 0;

	for (p = str + 18; p && *p == ';'; p = strchr(p + 1, ';')) {
		val = NULL;
		switch (p[1]) {
			case 'S':
				switch (p[2]) {
					case 'X':
						val = &sx;
						break;
					case 'Y':
						val = &sy;
						break;
					case 'W':
						val = &sw;
						break;
					case 'H':
						val = &sh;
						break;
				}
				break;
			case 'D':
				switch (p[2]) {
					case 'X':
						val = &dx;
						break;
					case 'Y':
						val = &dy;
						break;
				}
				break;
			case 'M':
				if (p[2] == 'X') {
					val = &mx;
					break;
				}
				if (p[2] == 'Y') {
					val = &my;
					break;
				}
				if (p[2] == 'W') {
					val = &mw;
					break;
				}
				if (p[2] == 'H') {
					val = &mh;
					break;
				}
				if (strncmp(p + 2, "FILE=", 5) == 0) {
					p2 = strchr(p + 7, ';');
					if (p2 == NULL)
						goto done;
					if (!mbuf)
						freemask(ctmask);
					mbuf = false;
					ctmask = NULL;
					free(mask);
					mask = strndup(p + 7, p2 - p - 7);
					continue; // Avoid val check
				}
				else if (strncmp(p + 2, "ASK=", 4) == 0) {
					p2 = strchr(p + 6, ';');
					if (p2 == NULL)
						goto done;
					FREE_AND_NULL(mask);
					if (!mbuf)
						freemask(ctmask);
					mbuf = false;
					ctmask = alloc_ciolib_mask(0, 0);
					ctmask->bits = b64_decode_alloc(p + 6, p2 - p + 5, &mlen);
					if (ctmask->bits == NULL)
						goto done;
					continue; // Avoid val check
				}
				else if (strncmp(p + 2, "BUF", 3) == 0) {
					freemask(ctmask);
					ctmask = NULL;
					mbuf = true;
					continue; // Avoid val check
				}
				break;
		}
		if (val == NULL || p[3] != '=')
			break;
		*val = strtoul(p + 4, NULL, 10);
	}

	if (asprintf(&ppmfn, "%s%s", fn, p + 1) == -1)
		goto done;
	ppmp = read_pbm(ppmfn, false);
	if (ppmp == NULL)
		goto done;

	if (sw == 0)
		sw = ppmp->width - sx;
	if (sh == 0)
		sh = ppmp->height - sy;

	if (ctmask != NULL) {
		if (mlen < (sw * sh + 7) / 8)
			goto done;
		if (mw == 0)
			mw = sw;
		if (mh == 0)
			mh = sh;
		if (mlen < (mw * mh + 7) / 8)
			goto done;
		ctmask->width = mw;
		ctmask->height = mh;
	}

	if (mask != NULL) {
		if (asprintf(&maskfn, "%s%s", fn, mask) < 0)
			goto done;
	}

	if (maskfn != NULL) {
		freemask(ctmask);
		ctmask = read_pbm(maskfn, true);
		if (ctmask == NULL)
			goto done;
		if (ctmask->width < sw || ctmask->height < sh)
			goto done;
	}

	if (mbuf)
		ctmask = mask_buffer;

	if (ppmp != NULL)
		setpixels(dx, dy, dx + sw - 1, dy + sh - 1, sx, sy, mx, my, ppmp, ctmask);
done:
	free(mask);
	free(maskfn);
	if (!mbuf)
		freemask(ctmask);
	free(ppmfn);
	freepixels(ppmp);
}

static void
load_ppm_str_handler(char *str, size_t slen, char *fn, void *apcd)
{
	char                 *p;
	char                 *ppmfn = NULL;
	struct ciolib_pixels *ppmp = NULL;
	unsigned long         bufnum = 0;
	unsigned long        *val;

	for (p = str + 18; p && *p == ';'; p = strchr(p + 1, ';')) {
		val = NULL;
		switch (p[1]) {
			case 'B':
				val = &bufnum;
				break;
		}
		if (val == NULL || p[2] != '=')
			break;
		*val = strtoul(p + 3, NULL, 10);
	}

	if (bufnum >= sizeof(pixmap_buffer) / sizeof(pixmap_buffer[0]))