extern GPrivate *glk_data_key;
-/* Internal function: create a fileref using the given parameters. */
+/* Internal function: create a fileref using the given parameters. If @basename
+is NULL, compute a basename from @filename. */
frefid_t
-fileref_new(gchar *filename, glui32 rock, glui32 usage, glui32 orig_filemode)
+fileref_new(char *filename, char *basename, glui32 rock, glui32 usage, glui32 orig_filemode)
{
g_return_val_if_fail(filename != NULL, NULL);
f->disprock = (*glk_data->register_obj)(f, gidisp_Class_Fileref);
f->filename = g_strdup(filename);
+ if(basename)
+ f->basename = g_strdup(basename);
+ else
+ f->basename = g_path_get_basename(filename);
f->usage = usage;
f->orig_filemode = orig_filemode;
fref->disprock.ptr = NULL;
}
- if(fref->filename)
- g_free(fref->filename);
+ g_free(fref->filename);
+ g_free(fref->basename);
fref->magic = MAGIC_FREE;
g_free(fref);
return NULL;
}
- frefid_t f = fileref_new(filename, rock, usage, filemode_Write);
+ /* Pass a basename of "" to ensure that this file can't be repurposed */
+ frefid_t f = fileref_new(filename, "", rock, usage, filemode_Write);
g_free(filename);
return f;
}
return NULL;
}
+ /* Set up a file filter with suggested extensions */
+ GtkFileFilter *filter = gtk_file_filter_new();
+ switch(usage & fileusage_TypeMask)
+ {
+ case fileusage_Data:
+ gtk_file_filter_set_name(filter, _("Data files (*.glkdata)"));
+ gtk_file_filter_add_pattern(filter, "*.glkdata");
+ break;
+ case fileusage_SavedGame:
+ gtk_file_filter_set_name(filter, _("Saved games (*.glksave)"));
+ gtk_file_filter_add_pattern(filter, "*.glksave");
+ break;
+ case fileusage_InputRecord:
+ gtk_file_filter_set_name(filter, _("Text files (*.txt)"));
+ gtk_file_filter_add_pattern(filter, "*.txt");
+ break;
+ case fileusage_Transcript:
+ gtk_file_filter_set_name(filter, _("Transcript files (*.txt)"));
+ gtk_file_filter_add_pattern(filter, "*.txt");
+ break;
+ default:
+ ILLEGAL_PARAM("Unknown file usage: %u", usage);
+ gdk_threads_leave();
+ return NULL;
+ }
+ gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(chooser), filter);
+
+ /* Add a "text mode" filter for text files */
+ if((usage & fileusage_TypeMask) == fileusage_InputRecord || (usage & fileusage_TypeMask) == fileusage_Transcript)
+ {
+ filter = gtk_file_filter_new();
+ gtk_file_filter_set_name(filter, _("All text files"));
+ gtk_file_filter_add_mime_type(filter, "text/plain");
+ gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(chooser), filter);
+ }
+
+ /* Add another non-restricted filter */
+ filter = gtk_file_filter_new();
+ gtk_file_filter_set_name(filter, _("All files"));
+ gtk_file_filter_add_pattern(filter, "*");
+ gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(chooser), filter);
+
if(glk_data->current_dir)
gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(chooser), glk_data->current_dir);
return NULL;
}
gchar *filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(chooser) );
- frefid_t f = fileref_new(filename, rock, usage, fmode);
+ frefid_t f = fileref_new(filename, NULL, rock, usage, fmode);
g_free(filename);
gtk_widget_destroy(chooser);
ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
/* Do any string-munging here to remove illegal Latin-1 characters from
- filename. On ext3, the only illegal characters are '/' and '\0'. */
- g_strdelimit(name, "/", '_');
+ filename. On ext3, the only illegal characters are '/' and '\0', but the Glk
+ spec calls for removing any other tricky characters. */
+ char *buf = g_malloc(strlen(name));
+ char *ptr, *filename, *extension;
+ int len;
+ for(ptr = name, len = 0; *ptr && *ptr != '.'; ptr++)
+ {
+ switch(*ptr)
+ {
+ case '"': case '\\': case '/': case '>': case '<':
+ case ':': case '|': case '?': case '*':
+ break;
+ default:
+ buf[len++] = *ptr;
+ }
+ }
+ buf[len] = '\0';
+
+ /* If there is nothing left, make the name "null" */
+ if(len == 0) {
+ strcpy(buf, "null");
+ len = strlen(buf);
+ }
+
+ switch(usage & fileusage_TypeMask)
+ {
+ case fileusage_Data:
+ extension = ".glkdata";
+ break;
+ case fileusage_SavedGame:
+ extension = ".glksave";
+ break;
+ case fileusage_InputRecord:
+ case fileusage_Transcript:
+ extension = ".txt";
+ break;
+ default:
+ ILLEGAL_PARAM("Unknown file usage: %u", usage);
+ return NULL;
+ }
+ filename = g_strconcat(buf, extension, NULL);
/* Find out what encoding filenames are in */
const gchar **charsets; /* Do not free */
/* Convert name to that encoding */
GError *error = NULL;
- gchar *osname = g_convert(name, -1, charsets[0], "ISO-8859-1", NULL, NULL,
- &error);
+ char *osname = g_convert(filename, -1, charsets[0], "ISO-8859-1", NULL, NULL, &error);
if(osname == NULL)
{
WARNING_S("Error during latin1->filename conversion", error->message);
path = g_strdup(osname);
g_free(osname);
- frefid_t f = fileref_new(path, rock, usage, filemode_ReadWrite);
+ frefid_t f = fileref_new(path, buf, rock, usage, filemode_ReadWrite);
g_free(path);
+ g_free(buf);
return f;
}
glk_fileref_create_from_fileref(glui32 usage, frefid_t fref, glui32 rock)
{
VALID_FILEREF(fref, return NULL);
- return fileref_new(fref->filename, rock, usage, fref->orig_filemode);
+ return fileref_new(fref->filename, fref->basename, rock, usage, fref->orig_filemode);
}
/**
/* Fileref parameters */
gchar *filename; /* Always stored in the default filename encoding, not
UTF8 or Latin-1 */
+ char *basename; /* Name from which real filename was derived */
glui32 orig_filemode; /* Used to check if the user gets a fileref in read
mode and then tries to open it in write mode */
glui32 usage;
};
-G_GNUC_INTERNAL frefid_t fileref_new(gchar *filename, glui32 rock, glui32 usage, glui32 orig_filemode);
+G_GNUC_INTERNAL frefid_t fileref_new(char *filename, char *basename, glui32 rock, glui32 usage, glui32 orig_filemode);
#endif
case gestalt_UnicodeNorm:
case gestalt_LineInputEcho:
case gestalt_LineTerminators:
+ case gestalt_ResourceStream:
return 1;
/* Capabilities supported if compiled with GStreamer */
#endif
/* Unsupported capabilities */
- case gestalt_ResourceStream:
- return 0;
+ /* None! */
/* Selector not supported */
default:
g_return_val_if_fail(pathname, NULL);
g_return_val_if_fail(strlen(pathname) > 0, NULL);
- frefid_t fileref = fileref_new(pathname, rock,
+ frefid_t fileref = fileref_new(pathname, NULL, rock,
textmode? fileusage_TextMode : fileusage_BinaryMode,
writemode? filemode_Write : filemode_Read);
return file_stream_new(fileref, writemode? filemode_Write : filemode_Read, rock, FALSE);
g_return_val_if_fail(pathname, NULL);
g_return_val_if_fail(strlen(pathname) > 0, NULL);
- frefid_t fileref = fileref_new(pathname, rock, textmode? fileusage_TextMode : fileusage_BinaryMode, filemode_Read);
+ frefid_t fileref = fileref_new(pathname, NULL, rock, textmode? fileusage_TextMode : fileusage_BinaryMode, filemode_Read);
return file_stream_new(fileref, filemode_Read, rock, FALSE);
}
g_slist_free(arglist);
return TRUE;
-}
\ No newline at end of file
+}
+#include <config.h>
#include "stream.h"
#include "fileref.h"
#include "magic.h"
+#include "gi_blorb.h"
#include <errno.h>
#include <stdio.h>
#include <glib.h>
#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
#include "chimara-glk-private.h"
extern GPrivate *glk_data_key;
strid_t
glk_stream_open_resource(glui32 filenum, glui32 rock)
{
- g_warning("Not implemented");
- return NULL;
+ /* Adapted from CheapGlk */
+ strid_t str;
+ gboolean isbinary;
+ giblorb_err_t err;
+ giblorb_result_t res;
+ giblorb_map_t *map = giblorb_get_resource_map();
+ if(map == NULL) {
+ WARNING(_("Could not create resource stream, because there was no "
+ "resource map."));
+ return NULL; /* Not running from a blorb file */
+ }
+
+ err = giblorb_load_resource(map, giblorb_method_Memory, &res, giblorb_ID_Data, filenum);
+ if(err) {
+ WARNING_S(_("Could not create resource stream, because the resource "
+ "could not be loaded"), giblorb_get_error_message(err));
+ return 0; /* Not found, or some other error */
+ }
+
+ /* We'll use the in-memory copy of the chunk data as the basis for
+ our new stream. It's important to not call chunk_unload() until
+ the stream is closed (and we won't).
+
+ This will be memory-hoggish for giant data chunks, but I don't
+ expect giant data chunks at this point. A more efficient model
+ would be to use the file on disk, but this requires some hacking
+ into the file stream code (we'd need to open a new FILE*) and
+ I don't feel like doing that. */
+
+ if(res.chunktype == giblorb_ID_TEXT)
+ isbinary = FALSE;
+ else if(res.chunktype == giblorb_ID_BINA)
+ isbinary = TRUE;
+ else {
+ WARNING(_("Could not create resource stream, because chunk was of "
+ "unknown type."));
+ return NULL; /* Unknown chunk type */
+ }
+
+ str = stream_new_common(rock);
+ str->type = STREAM_TYPE_RESOURCE;
+ str->file_mode = filemode_Read;
+ str->binary = isbinary;
+
+ if (res.data.ptr && res.length) {
+ str->buffer = res.data.ptr;
+ str->mark = 0;
+ str->buflen = res.length;
+ str->endmark = str->buflen;
+ }
+
+ return str;
}
/**
strid_t
glk_stream_open_resource_uni(glui32 filenum, glui32 rock)
{
- g_warning("Not implemented");
- return NULL;
+ /* Adapted from CheapGlk */
+ /* We have been handed an array of bytes. (They're big-endian
+ four-byte chunks, or perhaps a UTF-8 byte sequence, rather than
+ native-endian four-byte integers). So we drop it into str->buffer,
+ rather than str->ubuffer -- we'll have to do the translation in the
+ get() functions. */
+ strid_t str = glk_stream_open_resource(filenum, rock);
+ if(str != NULL)
+ str->unicode = TRUE;
+ return str;
}
/**
IO_WARNING( "Failed to close file", str->filename, g_strerror(errno) );
g_free(str->filename);
break;
+
+ case STREAM_TYPE_RESOURCE:
+ /* Shouldn't free the chunk; someone else might be using it. It will
+ be freed when the resource map is freed. */
+ break;
+
default:
ILLEGAL_PARAM("Unknown stream type: %u", str->type);
return;
{
STREAM_TYPE_WINDOW,
STREAM_TYPE_MEMORY,
- STREAM_TYPE_FILE
+ STREAM_TYPE_FILE,
+ STREAM_TYPE_RESOURCE
};
/**
enum StreamType type;
/* Specific to window stream: the window this stream is connected to */
winid_t window;
- /* For memory and file streams */
+ /* For memory, file, and resource streams */
gboolean unicode;
- /* Specific to memory streams */
- gchar *buffer;
+ /* For file and resource streams */
+ gboolean binary;
+ /* For memory and resource streams */
+ char *buffer;
glui32 *ubuffer;
glui32 mark;
glui32 endmark;
glui32 buflen;
+ /* Specific to memory streams */
gidispatch_rock_t buffer_rock;
/* Specific to file streams */
FILE *file_pointer;
- gboolean binary;
gchar *filename; /* Displayable filename in UTF-8 for error handling */
glui32 lastop; /* 0, filemode_Write, or filemode_Read */
+#include <config.h>
#include "charset.h"
#include "magic.h"
#include "stream.h"
#include <string.h>
#include <glib.h>
#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
/* Internal function: ensure that an fseek() is called on a file pointer in
between reading and writing operations, and vice versa. This will only come up
str->write_count += len;
break;
+ case STREAM_TYPE_RESOURCE:
+ ILLEGAL(_("Writing to a resource stream is illegal."));
+ break;
default:
ILLEGAL_PARAM("Unknown stream type: %u", str->type);
}
str->write_count += len;
break;
+ case STREAM_TYPE_RESOURCE:
+ ILLEGAL(_("Writing to a resource stream is illegal."));
+ break;
default:
ILLEGAL_PARAM("Unknown stream type: %u", str->type);
}
return charresult;
}
+/* Internal function: Read one UTF-8 character, which may be more than one byte,
+from a memory stream @str, and return it as a Unicode code point. */
+static glsi32
+read_utf8_char_from_buffer(strid_t str)
+{
+ size_t foo;
+ gunichar charresult = (gunichar)-2;
+ char *buffer = str->buffer + str->mark;
+ size_t maxlen = str->buflen - str->mark;
+
+ if(maxlen == 0)
+ return -1;
+
+ for(foo = 1; foo <= maxlen; foo++)
+ {
+ charresult = g_utf8_get_char_validated(buffer, foo);
+ /* charresult is -1 if invalid, -2 if incomplete, and the
+ Unicode code point otherwise */
+ if(charresult != (gunichar)-2)
+ break;
+ }
+ str->mark += foo;
+ str->read_count++;
+
+ /* Return -1 on EOS */
+ if(charresult == (gunichar)-2)
+ return -1;
+ /* Silently return unknown characters as 0xFFFD, Replacement Character */
+ if(charresult == (gunichar)-1)
+ return 0xFFFD;
+ return charresult;
+}
+
+/* Internal function: Read one big-endian four-byte character from memory and
+return it as a Unicode code point, or -1 on EOF */
+static glsi32
+read_ucs4be_char_from_buffer(strid_t str)
+{
+ glui32 ch = str->buffer[str->mark++];
+ if(str->mark >= str->buflen)
+ return -1;
+ ch = (ch << 8) | (str->buffer[str->mark++] & 0xFF);
+ if(str->mark >= str->buflen)
+ return -1;
+ ch = (ch << 8) | (str->buffer[str->mark++] & 0xFF);
+ if(str->mark >= str->buflen)
+ return -1;
+ ch = (ch << 8) | (str->buffer[str->mark++] & 0xFF);
+ str->read_count++;
+ return ch;
+}
+
/* Internal function: Tell whether this code point is a Unicode newline. The
file pointer and eight-bit flag are included in case the newline is a CR
(U+000D). If the next character is LF (U+000A) then it also belongs to the
{
switch(str->type)
{
+ case STREAM_TYPE_RESOURCE:
+ if(str->unicode)
+ {
+ if(!str->buffer || str->mark >= str->buflen)
+ return -1;
+ if(str->binary)
+ /* Cheap big-endian stream */
+ return read_ucs4be_char_from_buffer(str);
+ /* slightly less cheap UTF8 stream */
+ return read_utf8_char_from_buffer(str);
+ }
+ /* for text streams, fall through to memory case */
case STREAM_TYPE_MEMORY:
if(str->unicode)
{
switch(str->type)
{
+ case STREAM_TYPE_RESOURCE:
+ {
+ int copycount = 0;
+ if(str->unicode)
+ {
+ while(copycount < len && str->buffer && str->mark < str->buflen)
+ {
+ glui32 ch;
+ if(str->binary)
+ ch = read_ucs4be_char_from_buffer(str);
+ else
+ ch = read_utf8_char_from_buffer(str);
+ buf[copycount++] = (ch > 0xFF)? '?' : (char)ch;
+ }
+ return copycount;
+ }
+ /* for text streams, fall through to memory case */
+ }
case STREAM_TYPE_MEMORY:
{
int copycount = 0;
switch(str->type)
{
+ case STREAM_TYPE_RESOURCE:
+ {
+ int copycount = 0;
+ if(str->unicode)
+ {
+ while(copycount < len && str->buffer && str->mark < str->buflen)
+ {
+ glui32 ch;
+ if(str->binary)
+ ch = read_ucs4be_char_from_buffer(str);
+ else
+ ch = read_utf8_char_from_buffer(str);
+ buf[copycount++] = ch;
+ }
+ return copycount;
+ }
+ /* for text streams, fall through to memory case */
+ }
case STREAM_TYPE_MEMORY:
{
int copycount = 0;
switch(str->type)
{
+ case STREAM_TYPE_RESOURCE:
+ {
+ int copycount = 0;
+ if(str->unicode)
+ {
+ /* Do it character-by-character */
+ while(copycount < len - 1 && str->buffer && str->mark < str->buflen)
+ {
+ glsi32 ch;
+ if(str->binary)
+ ch = read_ucs4be_char_from_buffer(str);
+ else
+ ch = read_utf8_char_from_buffer(str);
+ /* Check for Unicode newline; slightly different than
+ in file streams */
+ if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029)
+ {
+ buf[copycount++] = '\n';
+ break;
+ }
+ if(ch == 0x0D)
+ {
+ if(str->buffer[str->mark] == 0x0A)
+ str->mark++; /* skip past next newline */
+ buf[copycount++] = '\n';
+ break;
+ }
+ buf[copycount++] = (ch > 0xFF)? '?' : (char)ch;
+ }
+ buf[copycount] = '\0';
+ return copycount;
+ }
+ /* for text streams, fall through to the memory case */
+ }
case STREAM_TYPE_MEMORY:
{
int copycount = 0;
switch(str->type)
{
+ case STREAM_TYPE_RESOURCE:
+ {
+ int copycount = 0;
+ if(str->unicode)
+ {
+ /* Do it character-by-character */
+ while(copycount < len - 1 && str->buffer && str->mark < str->buflen)
+ {
+ glsi32 ch;
+ if(str->binary)
+ ch = read_ucs4be_char_from_buffer(str);
+ else
+ ch = read_utf8_char_from_buffer(str);
+ /* Check for Unicode newline; slightly different than
+ in file streams */
+ if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029)
+ {
+ buf[copycount++] = '\n';
+ break;
+ }
+ if(ch == 0x0D)
+ {
+ if(str->ubuffer[str->mark] == 0x0A)
+ str->mark++; /* skip past next newline */
+ buf[copycount++] = '\n';
+ break;
+ }
+ buf[copycount++] = ch;
+ }
+ buf[copycount] = '\0';
+ return copycount;
+ }
+ /* for text streams, fall through to the memory case */
+ }
case STREAM_TYPE_MEMORY:
{
int copycount = 0;
switch(str->type)
{
case STREAM_TYPE_MEMORY:
+ case STREAM_TYPE_RESOURCE:
return str->mark;
case STREAM_TYPE_FILE:
return ftell(str->file_pointer);
switch(str->type)
{
+ case STREAM_TYPE_RESOURCE:
case STREAM_TYPE_MEMORY:
switch(seekmode)
{
<?xml version="1.0"?>
<interface>
- <!-- interface-requires gtk+ 2.12 -->
+ <requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy project-wide -->
<object class="GtkWindow" id="window">
<property name="border_width">6</property>
<col id="0" translatable="yes">MemStreamTest</col>
<col id="1">memstreamtest.ulx</col>
</row>
+ <row>
+ <col id="0" translatable="yes">ResStreamTest</col>
+ <col id="1">resstreamtest.gblorb</col>
+ </row>
<row>
<col id="0" translatable="yes">UniCaseTest</col>
<col id="1">unicasetest.ulx</col>