2 * Copyright 2010-2012 Chris Spiegel.
4 * This file is part of Bocfel.
6 * Bocfel is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License, version
8 * 2 or 3, as published by the Free Software Foundation.
10 * Bocfel is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with Bocfel. If not, see <http://www.gnu.org/licenses/>.
37 /* Generally speaking, UNICODE_LINEFEED (10) is used as a newline. Glk
38 * requires this (Glk API 0.7.0 §2.2), and when Unicode is available, we
39 * write characters out by hand even with stdio, so no translation can
40 * be done. However, when stdio is being used, Unicode is not
41 * available, and the file usage will be for a transcript or
42 * command-script, use '\n' as a newline so translation can be done;
43 * this is the only case where streams are opened in text mode.
45 * zterp_io_stdio() and zterp_io_stdout() are considered text-mode if
46 * Unicode is not available, binary otherwise.
48 #define textmode(io) (!use_utf8_io && ((io->mode) & (ZTERP_IO_TRANS | ZTERP_IO_INPUT)))
52 enum type { IO_STDIO, IO_GLK } type;
61 /* Glk does not like you to be able to pass a full filename to
62 * glk_fileref_create_by_name(); this means that Glk cannot be used to
63 * open arbitrary files. However, Glk is still required to prompt for
64 * files, such as in a save game situation. To allow zterp_io to work
65 * for opening files both with and without a prompt, it will use stdio
66 * when either Glk is not available, or when Glk is available but
67 * prompting is not necessary.
69 * This is needed because the IFF parser is required for both opening
70 * games (zblorb files) and for saving/restoring. The former needs to
71 * be able to access any file on the filesystem, and the latter needs to
72 * prompt. This is a headache.
74 * Prompting is assumed to be necessary if “filename” is NULL.
76 zterp_io *zterp_io_open(const char *filename, int mode)
81 fprintf(stderr, "zterp_io_open: '%s'\n", filename);
83 io = malloc(sizeof *io);
84 if(io == NULL) goto err;
87 if (mode & ZTERP_IO_RDONLY) smode[0] = 'r';
88 else if(mode & ZTERP_IO_APPEND) smode[0] = 'a';
90 if(textmode(io)) smode[1] = 0;
93 int usage = fileusage_BinaryMode, filemode;
95 if (mode & ZTERP_IO_SAVE) usage |= fileusage_SavedGame;
96 else if(mode & ZTERP_IO_TRANS) usage |= fileusage_Transcript;
97 else if(mode & ZTERP_IO_INPUT) usage |= fileusage_InputRecord;
98 else usage |= fileusage_Data;
100 if (mode & ZTERP_IO_RDONLY) filemode = filemode_Read;
101 else if(mode & ZTERP_IO_WRONLY) filemode = filemode_Write;
102 else if(mode & ZTERP_IO_APPEND) filemode = filemode_WriteAppend;
108 if (mode & ZTERP_IO_SAVE) prompt = "Enter filename for save game: ";
109 else if(mode & ZTERP_IO_TRANS) prompt = "Enter filename for transcript: ";
110 else if(mode & ZTERP_IO_INPUT) prompt = "Enter filename for command record: ";
111 else prompt = "Enter filename for data: ";
114 /* No need to prompt. */
118 io->fp = fopen(filename, smode);
119 if(io->fp == NULL) goto err;
127 ref = glk_fileref_create_by_prompt(usage, filemode, 0);
128 if(ref == NULL) goto err;
131 io->file = glk_stream_open_file(ref, filemode, 0);
132 glk_fileref_destroy(ref);
133 if(io->file == NULL) goto err;
135 char fn[MAX_PATH], *p;
137 printf("\n%s", prompt);
139 if(fgets(fn, sizeof fn, stdin) == NULL || fn[0] == '\n') goto err;
140 p = strchr(fn, '\n');
141 if(p != NULL) *p = 0;
144 io->fp = fopen(fn, smode);
145 if(io->fp == NULL) goto err;
157 /* The zterp_os_reopen_binary() calls attempt to reopen stdin/stdout as
158 * binary streams so that reading/writing UTF-8 doesn’t cause unwanted
159 * translations. The mode of ZTERP_IO_TRANS is set when Unicode is
160 * unavailable as a way to signal that these are text streams.
162 const zterp_io *zterp_io_stdin(void)
169 io.mode = ZTERP_IO_RDONLY;
170 if(use_utf8_io) zterp_os_reopen_binary(stdin);
171 else io.mode |= ZTERP_IO_TRANS;
178 const zterp_io *zterp_io_stdout(void)
185 io.mode = ZTERP_IO_WRONLY;
186 if(use_utf8_io) zterp_os_reopen_binary(stdout);
187 else io.mode |= ZTERP_IO_TRANS;
194 void zterp_io_close(zterp_io *io)
197 if(io->type == IO_GLK)
199 glk_stream_close(io->file, NULL);
210 int zterp_io_seek(const zterp_io *io, long offset, int whence)
212 /* To smooth over differences between Glk and standard I/O, don’t
213 * allow seeking in append-only streams.
215 if(io->mode & ZTERP_IO_APPEND) return -1;
218 if(io->type == IO_GLK)
220 glk_stream_set_position(io->file, offset, whence == SEEK_SET ? seekmode_Start : whence == SEEK_CUR ? seekmode_Current : seekmode_End);
221 return 0; /* dammit */
226 return fseek(io->fp, offset, whence);
230 long zterp_io_tell(const zterp_io *io)
233 if(io->type == IO_GLK)
235 return glk_stream_get_position(io->file);
240 return ftell(io->fp);
244 /* zterp_io_read() and zterp_io_write() always operate in terms of
245 * bytes, whether or not Unicode is available.
247 size_t zterp_io_read(const zterp_io *io, void *buf, size_t n)
250 if(io->type == IO_GLK)
252 glui32 s = glk_get_buffer_stream(io->file, buf, n);
253 /* This should only happen if io->file is invalid. */
254 if(s == (glui32)-1) s = 0;
260 return fread(buf, 1, n, io->fp);
264 size_t zterp_io_write(const zterp_io *io, const void *buf, size_t n)
267 if(io->type == IO_GLK)
269 glk_put_buffer_stream(io->file, (char *)buf, n);
270 return n; /* dammit */
275 return fwrite(buf, 1, n, io->fp);
279 int zterp_io_read16(const zterp_io *io, uint16_t *v)
283 if(zterp_io_read(io, buf, sizeof buf) != sizeof buf) return 0;
285 *v = (buf[0] << 8) | buf[1];
290 int zterp_io_read32(const zterp_io *io, uint32_t *v)
294 if(zterp_io_read(io, buf, sizeof buf) != sizeof buf) return 0;
296 *v = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
301 /* Read a byte and make sure it’s part of a valid UTF-8 sequence. */
302 static int read_byte(const zterp_io *io, uint8_t *c)
304 if(zterp_io_read(io, c, sizeof *c) != sizeof *c) return 0;
305 if((*c & 0x80) != 0x80) return 0;
310 /* zterp_io_getc() and zterp_io_putc() are meant to operate in terms of
311 * characters, not bytes. That is, unlike C, bytes and characters are
312 * not equivalent as far as Zterp’s I/O system is concerned.
315 /* Read a UTF-8 character, returning it.
316 * -1 is returned on EOF.
318 * If there is a problem reading the UTF-8 (either from an invalid
319 * sequence or from a too-large value), a question mark is returned.
321 * If Unicode is not available, read a single byte (assumed to be
323 * If Unicode is not available, IO_STDIO is in use, and text mode is
324 * set, do newline translation. Text mode is likely to always be
325 * set—this function really shouldn’t be used in binary mode.
327 long zterp_io_getc(const zterp_io *io)
334 if(io->type == IO_GLK)
336 ret = glk_get_char_stream(io->file);
344 if(c == EOF) ret = -1;
347 if(textmode(io) && c == '\n') ret = UNICODE_LINEFEED;
354 if(zterp_io_read(io, &c, sizeof c) != sizeof c)
358 else if((c & 0x80) == 0) /* One byte. */
362 else if((c & 0xe0) == 0xc0) /* Two bytes. */
364 ret = (c & 0x1f) << 6;
366 if(!read_byte(io, &c)) return UNICODE_QUESTIONMARK;
370 else if((c & 0xf0) == 0xe0) /* Three bytes. */
372 ret = (c & 0x0f) << 12;
374 if(!read_byte(io, &c)) return UNICODE_QUESTIONMARK;
376 ret |= ((c & 0x3f) << 6);
378 if(!read_byte(io, &c)) return UNICODE_QUESTIONMARK;
382 else if((c & 0xf8) == 0xf0) /* Four bytes. */
384 /* The Z-machine doesn’t support Unicode this large, but at
385 * least try not to leave a partial character in the stream.
387 zterp_io_seek(io, 3, SEEK_CUR);
389 ret = UNICODE_QUESTIONMARK;
391 else /* Invalid value. */
393 ret = UNICODE_QUESTIONMARK;
397 if(ret > UINT16_MAX) ret = UNICODE_QUESTIONMARK;
402 /* Write a Unicode character as UTF-8.
404 * If Unicode is not available, write the value out as a single Latin-1
405 * byte. If it is too large for a byte, write out a question mark.
407 * If Unicode is not available, IO_STDIO is in use, and text mode is
408 * set, do newline translation.
410 * Text mode is likely to always be set—this function really shouldn’t
411 * be used in binary mode.
413 void zterp_io_putc(const zterp_io *io, uint16_t c)
417 if(c > UINT8_MAX) c = UNICODE_QUESTIONMARK;
419 if(io->type == IO_GLK)
421 glk_put_char_stream(io->file, c);
426 if(textmode(io) && c == UNICODE_LINEFEED) c = '\n';
432 uint8_t hi = c >> 8, lo = c & 0xff;
434 #define WRITE(c) zterp_io_write(io, &(uint8_t){ c }, sizeof (uint8_t))
441 WRITE(0xc0 | (hi << 2) | (lo >> 6));
442 WRITE(0x80 | (lo & 0x3f));
446 WRITE(0xe0 | (hi >> 4));
447 WRITE(0x80 | ((hi << 2) & 0x3f) | (lo >> 6));
448 WRITE(0x80 | (lo & 0x3f));
454 long zterp_io_readline(const zterp_io *io, uint16_t *buf, size_t len)
458 if(len > LONG_MAX) return -1;
460 for(ret = 0; ret < len; ret++)
462 long c = zterp_io_getc(io);
464 /* EOF before newline means there was a problem. */
465 if(c == -1) return -1;
467 /* Don’t count the newline. */
468 if(c == UNICODE_LINEFEED) break;
476 long zterp_io_filesize(const zterp_io *io)
478 if(io->type == IO_STDIO && !textmode(io))
480 return zterp_os_filesize(io->fp);
488 void zterp_io_flush(const zterp_io *io)
490 if(io == NULL || io->type != IO_STDIO || !(io->mode & (ZTERP_IO_WRONLY | ZTERP_IO_APPEND))) return;