X-Git-Url: https://git.stderr.nl/gitweb?a=blobdiff_plain;f=interpreters%2Fbocfel%2Fio.c;fp=interpreters%2Fbocfel%2Fio.c;h=d51e65a183b0b31ead6ae7035fff64b57aff0922;hb=3c59ba5eef5cb4d39c06eb7f523b9c3b026bdc9b;hp=0000000000000000000000000000000000000000;hpb=ed91d840318ed6ebfe3a5a77fa17114ddbf56640;p=projects%2Fchimara%2Fchimara.git diff --git a/interpreters/bocfel/io.c b/interpreters/bocfel/io.c new file mode 100644 index 0000000..d51e65a --- /dev/null +++ b/interpreters/bocfel/io.c @@ -0,0 +1,493 @@ +/*- + * Copyright 2010-2012 Chris Spiegel. + * + * This file is part of Bocfel. + * + * Bocfel is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version + * 2 or 3, as published by the Free Software Foundation. + * + * Bocfel is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Bocfel. If not, see . + */ + +#include +#include +#include +#include +#include + +#ifdef ZTERP_GLK +#include +#endif + +#include "io.h" +#include "osdep.h" +#include "unicode.h" + +#define MAX_PATH 4096 + +int use_utf8_io; + +/* Generally speaking, UNICODE_LINEFEED (10) is used as a newline. Glk + * requires this (Glk API 0.7.0 §2.2), and when Unicode is available, we + * write characters out by hand even with stdio, so no translation can + * be done. However, when stdio is being used, Unicode is not + * available, and the file usage will be for a transcript or + * command-script, use '\n' as a newline so translation can be done; + * this is the only case where streams are opened in text mode. + * + * zterp_io_stdio() and zterp_io_stdout() are considered text-mode if + * Unicode is not available, binary otherwise. + */ +#define textmode(io) (!use_utf8_io && ((io->mode) & (ZTERP_IO_TRANS | ZTERP_IO_INPUT))) + +struct zterp_io +{ + enum type { IO_STDIO, IO_GLK } type; + + FILE *fp; + int mode; +#ifdef ZTERP_GLK + strid_t file; +#endif +}; + +/* Glk does not like you to be able to pass a full filename to + * glk_fileref_create_by_name(); this means that Glk cannot be used to + * open arbitrary files. However, Glk is still required to prompt for + * files, such as in a save game situation. To allow zterp_io to work + * for opening files both with and without a prompt, it will use stdio + * when either Glk is not available, or when Glk is available but + * prompting is not necessary. + * + * This is needed because the IFF parser is required for both opening + * games (zblorb files) and for saving/restoring. The former needs to + * be able to access any file on the filesystem, and the latter needs to + * prompt. This is a headache. + * + * Prompting is assumed to be necessary if “filename” is NULL. + */ +zterp_io *zterp_io_open(const char *filename, int mode) +{ + zterp_io *io; + char smode[] = "wb"; + + fprintf(stderr, "zterp_io_open: '%s'\n", filename); + + io = malloc(sizeof *io); + if(io == NULL) goto err; + io->mode = mode; + + if (mode & ZTERP_IO_RDONLY) smode[0] = 'r'; + else if(mode & ZTERP_IO_APPEND) smode[0] = 'a'; + + if(textmode(io)) smode[1] = 0; + +#ifdef ZTERP_GLK + int usage = fileusage_BinaryMode, filemode; + + if (mode & ZTERP_IO_SAVE) usage |= fileusage_SavedGame; + else if(mode & ZTERP_IO_TRANS) usage |= fileusage_Transcript; + else if(mode & ZTERP_IO_INPUT) usage |= fileusage_InputRecord; + else usage |= fileusage_Data; + + if (mode & ZTERP_IO_RDONLY) filemode = filemode_Read; + else if(mode & ZTERP_IO_WRONLY) filemode = filemode_Write; + else if(mode & ZTERP_IO_APPEND) filemode = filemode_WriteAppend; + + else goto err; +#else + const char *prompt; + + if (mode & ZTERP_IO_SAVE) prompt = "Enter filename for save game: "; + else if(mode & ZTERP_IO_TRANS) prompt = "Enter filename for transcript: "; + else if(mode & ZTERP_IO_INPUT) prompt = "Enter filename for command record: "; + else prompt = "Enter filename for data: "; +#endif + + /* No need to prompt. */ + if(filename != NULL) + { + io->type = IO_STDIO; + io->fp = fopen(filename, smode); + if(io->fp == NULL) goto err; + } + /* Prompt. */ + else + { +#ifdef ZTERP_GLK + frefid_t ref; + + ref = glk_fileref_create_by_prompt(usage, filemode, 0); + if(ref == NULL) goto err; + + io->type = IO_GLK; + io->file = glk_stream_open_file(ref, filemode, 0); + glk_fileref_destroy(ref); + if(io->file == NULL) goto err; +#else + char fn[MAX_PATH], *p; + + printf("\n%s", prompt); + fflush(stdout); + if(fgets(fn, sizeof fn, stdin) == NULL || fn[0] == '\n') goto err; + p = strchr(fn, '\n'); + if(p != NULL) *p = 0; + + io->type = IO_STDIO; + io->fp = fopen(fn, smode); + if(io->fp == NULL) goto err; +#endif + } + + return io; + +err: + free(io); + + return NULL; +} + +/* The zterp_os_reopen_binary() calls attempt to reopen stdin/stdout as + * binary streams so that reading/writing UTF-8 doesn’t cause unwanted + * translations. The mode of ZTERP_IO_TRANS is set when Unicode is + * unavailable as a way to signal that these are text streams. + */ +const zterp_io *zterp_io_stdin(void) +{ + static zterp_io io; + + if(io.fp == NULL) + { + io.type = IO_STDIO; + io.mode = ZTERP_IO_RDONLY; + if(use_utf8_io) zterp_os_reopen_binary(stdin); + else io.mode |= ZTERP_IO_TRANS; + io.fp = stdin; + } + + return &io; +} + +const zterp_io *zterp_io_stdout(void) +{ + static zterp_io io; + + if(io.fp == NULL) + { + io.type = IO_STDIO; + io.mode = ZTERP_IO_WRONLY; + if(use_utf8_io) zterp_os_reopen_binary(stdout); + else io.mode |= ZTERP_IO_TRANS; + io.fp = stdout; + } + + return &io; +} + +void zterp_io_close(zterp_io *io) +{ +#ifdef ZTERP_GLK + if(io->type == IO_GLK) + { + glk_stream_close(io->file, NULL); + } + else +#endif + { + fclose(io->fp); + } + + free(io); +} + +int zterp_io_seek(const zterp_io *io, long offset, int whence) +{ + /* To smooth over differences between Glk and standard I/O, don’t + * allow seeking in append-only streams. + */ + if(io->mode & ZTERP_IO_APPEND) return -1; + +#ifdef ZTERP_GLK + if(io->type == IO_GLK) + { + glk_stream_set_position(io->file, offset, whence == SEEK_SET ? seekmode_Start : whence == SEEK_CUR ? seekmode_Current : seekmode_End); + return 0; /* dammit */ + } + else +#endif + { + return fseek(io->fp, offset, whence); + } +} + +long zterp_io_tell(const zterp_io *io) +{ +#ifdef ZTERP_GLK + if(io->type == IO_GLK) + { + return glk_stream_get_position(io->file); + } + else +#endif + { + return ftell(io->fp); + } +} + +/* zterp_io_read() and zterp_io_write() always operate in terms of + * bytes, whether or not Unicode is available. + */ +size_t zterp_io_read(const zterp_io *io, void *buf, size_t n) +{ +#ifdef ZTERP_GLK + if(io->type == IO_GLK) + { + glui32 s = glk_get_buffer_stream(io->file, buf, n); + /* This should only happen if io->file is invalid. */ + if(s == (glui32)-1) s = 0; + return s; + } + else +#endif + { + return fread(buf, 1, n, io->fp); + } +} + +size_t zterp_io_write(const zterp_io *io, const void *buf, size_t n) +{ +#ifdef ZTERP_GLK + if(io->type == IO_GLK) + { + glk_put_buffer_stream(io->file, (char *)buf, n); + return n; /* dammit */ + } + else +#endif + { + return fwrite(buf, 1, n, io->fp); + } +} + +int zterp_io_read16(const zterp_io *io, uint16_t *v) +{ + uint8_t buf[2]; + + if(zterp_io_read(io, buf, sizeof buf) != sizeof buf) return 0; + + *v = (buf[0] << 8) | buf[1]; + + return 1; +} + +int zterp_io_read32(const zterp_io *io, uint32_t *v) +{ + uint8_t buf[4]; + + if(zterp_io_read(io, buf, sizeof buf) != sizeof buf) return 0; + + *v = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; + + return 1; +} + +/* Read a byte and make sure it’s part of a valid UTF-8 sequence. */ +static int read_byte(const zterp_io *io, uint8_t *c) +{ + if(zterp_io_read(io, c, sizeof *c) != sizeof *c) return 0; + if((*c & 0x80) != 0x80) return 0; + + return 1; +} + +/* zterp_io_getc() and zterp_io_putc() are meant to operate in terms of + * characters, not bytes. That is, unlike C, bytes and characters are + * not equivalent as far as Zterp’s I/O system is concerned. + */ + +/* Read a UTF-8 character, returning it. + * -1 is returned on EOF. + * + * If there is a problem reading the UTF-8 (either from an invalid + * sequence or from a too-large value), a question mark is returned. + * + * If Unicode is not available, read a single byte (assumed to be + * Latin-1). + * If Unicode is not available, IO_STDIO is in use, and text mode is + * set, do newline translation. Text mode is likely to always be + * set—this function really shouldn’t be used in binary mode. + */ +long zterp_io_getc(const zterp_io *io) +{ + long ret; + + if(!use_utf8_io) + { +#ifdef ZTERP_GLK + if(io->type == IO_GLK) + { + ret = glk_get_char_stream(io->file); + } + else +#endif + { + int c; + + c = getc(io->fp); + if(c == EOF) ret = -1; + else ret = c; + + if(textmode(io) && c == '\n') ret = UNICODE_LINEFEED; + } + } + else + { + uint8_t c; + + if(zterp_io_read(io, &c, sizeof c) != sizeof c) + { + ret = -1; + } + else if((c & 0x80) == 0) /* One byte. */ + { + ret = c; + } + else if((c & 0xe0) == 0xc0) /* Two bytes. */ + { + ret = (c & 0x1f) << 6; + + if(!read_byte(io, &c)) return UNICODE_QUESTIONMARK; + + ret |= (c & 0x3f); + } + else if((c & 0xf0) == 0xe0) /* Three bytes. */ + { + ret = (c & 0x0f) << 12; + + if(!read_byte(io, &c)) return UNICODE_QUESTIONMARK; + + ret |= ((c & 0x3f) << 6); + + if(!read_byte(io, &c)) return UNICODE_QUESTIONMARK; + + ret |= (c & 0x3f); + } + else if((c & 0xf8) == 0xf0) /* Four bytes. */ + { + /* The Z-machine doesn’t support Unicode this large, but at + * least try not to leave a partial character in the stream. + */ + zterp_io_seek(io, 3, SEEK_CUR); + + ret = UNICODE_QUESTIONMARK; + } + else /* Invalid value. */ + { + ret = UNICODE_QUESTIONMARK; + } + } + + if(ret > UINT16_MAX) ret = UNICODE_QUESTIONMARK; + + return ret; +} + +/* Write a Unicode character as UTF-8. + * + * If Unicode is not available, write the value out as a single Latin-1 + * byte. If it is too large for a byte, write out a question mark. + * + * If Unicode is not available, IO_STDIO is in use, and text mode is + * set, do newline translation. + * + * Text mode is likely to always be set—this function really shouldn’t + * be used in binary mode. + */ +void zterp_io_putc(const zterp_io *io, uint16_t c) +{ + if(!use_utf8_io) + { + if(c > UINT8_MAX) c = UNICODE_QUESTIONMARK; +#ifdef ZTERP_GLK + if(io->type == IO_GLK) + { + glk_put_char_stream(io->file, c); + } + else +#endif + { + if(textmode(io) && c == UNICODE_LINEFEED) c = '\n'; + putc(c, io->fp); + } + } + else + { + uint8_t hi = c >> 8, lo = c & 0xff; + +#define WRITE(c) zterp_io_write(io, &(uint8_t){ c }, sizeof (uint8_t)) + if(c < 128) + { + WRITE(c); + } + else if(c < 2048) + { + WRITE(0xc0 | (hi << 2) | (lo >> 6)); + WRITE(0x80 | (lo & 0x3f)); + } + else + { + WRITE(0xe0 | (hi >> 4)); + WRITE(0x80 | ((hi << 2) & 0x3f) | (lo >> 6)); + WRITE(0x80 | (lo & 0x3f)); + } +#undef WRITE + } +} + +long zterp_io_readline(const zterp_io *io, uint16_t *buf, size_t len) +{ + long ret; + + if(len > LONG_MAX) return -1; + + for(ret = 0; ret < len; ret++) + { + long c = zterp_io_getc(io); + + /* EOF before newline means there was a problem. */ + if(c == -1) return -1; + + /* Don’t count the newline. */ + if(c == UNICODE_LINEFEED) break; + + buf[ret] = c; + } + + return ret; +} + +long zterp_io_filesize(const zterp_io *io) +{ + if(io->type == IO_STDIO && !textmode(io)) + { + return zterp_os_filesize(io->fp); + } + else + { + return -1; + } +} + +void zterp_io_flush(const zterp_io *io) +{ + if(io == NULL || io->type != IO_STDIO || !(io->mode & (ZTERP_IO_WRONLY | ZTERP_IO_APPEND))) return; + + fflush(io->fp); +}