Extending Glk to read resources from arbitrary files

Hugo and Adrift (before version 5) games support multimedia, but since they’re older formats, they don’t use Blorb. Instead, resources are embedded either in the story file itself or some other external file(s).

The Glk port of Hugo worked around this by writing out PIC and SND files, which wind up littering the game directory. The Glk port of Scare (Adrift interpreter) has the capacity to pop up images using an external image viewer, again using a temporary file, and doesn’t support sound at all.

I’ve been doing work in Gargoyle to improve this process, and Dannii and I have been having discussions here and here regarding its implementation.

Right now, the proposed implementation is a new Glk/Garglk API call that looks like this:

glui32 garglk_add_resource_from_file(glui32 usage, const char *filename, glui32 offset, glui32 len);

usage is either giblorb_ID_Pict or giblorb_ID_Snd and the rest specify where to read the resource from. The return value is 0 on failure, or a resource ID that can be used in future calls to Glk drawing/sound playing functions. This is guarded by the macro GLK_MODULE_GARGLK_FILE_RESOURCES.

We’re looking for feedback from anybody interested in this area of Glk. The goal here is to support older formats, and not be some general-purpose “replacement” for Blorb. New formats which have any desire to be usable with Glk should be using Blorb (as was done, for example, with Adrift 5).

4 Likes

One implementation note/detail: it’s my stance that the Glk library should cache all successful calls to this function, keyed on (usage, filename, offset, len), so that any future calls return the same ID (and this is what the proposed Gargoyle change does).

Rationale: the interpreters for which this was created always look things up this way (by offset in a file), and the way Glk works means even if you wanted to re-read the file each time the same resource was requested, you really can’t in a reasonable fashion: loading a resource is a separate action from using the resource. You’d need a new call to free the resource.

And if the Glk implementation doesn’t cache the lookups, then the interpreter would have to do it instead. Since that means duplicate code for each interpreter, it’s better to have the work done in a single place.

(Disclaimer: I’m not an interpreter implementer, just a user interested in what story files can do to my system. So I might be out of touch with conventions/consensus in this area. Sorry if this is derailing.)

While this extension is designed for use by interpreters, rather than for direct use by story files, if adopted in Glk proper (is that’s what proposed?) it would presumably become available to Glulx story files nevertheless (since I think they can access the whole Glk API), so security concerns seem relevant.
(Also relevant if the filename comes directly from the untrusted Hugo/Adrift story file; I don’t know whether that’s the case.)

What’s the plan for security/sandboxing, to restrict which filenames (gar)glk_add_resource_from_file() can access?
Will the accessible files be (effectively) limited to a single directory, such as the same one the main story file came from, which I understand to usually be the case for Glk arbitrary file access today (per Glk spec suggestions for glk_fileref_create_by_name(), and my reading of e.g. the glkterm implementation)?
Will there be a conventional filename suffix, like .glkres or something?
Do these sorts of restrictions conflict with the Hugo/Scare use cases?

(A quick skim of the code changes in PR#824 suggests there aren’t any restrictions right now.)

Working out how to use an unrestricted version of this API call to get up to mischief requires a little imagination, but I’d be uncomfortable with it having free rein.
(Off the top of my head, the ability for a story file to play an arbitrary sound file from anywhere on my system – or indeed any file – through the speakers could be less than ideal.)

Also, I’m curious how this would work for Parchment – where would it read external resource files from, such that they can be referred to by a filename?
(But that’s probably not germane to the proposed Glk API extension.)

1 Like

We’re not currently intending to add it to the dispatch layer, so it wouldn’t be usable by a Glulx storyfile. The Glk FileRef restrictions therefore won’t apply.

If the interpreter can’t access the filename then it will return 0. And as this is only an interpreter function, the interpreter could just directly open the file itself, so it doesn’t seem to be a security risk. It doesn’t let the storyfile read that data from the filename it requests, so you couldn’t use this to access someone’s SSH keys or whatever.

Parchment will need some work to support loading files relative to the storyfile URL. :wink:

2 Likes

Yes, this is a good point. As Dannii says, this doesn’t actually allow the story files to have access to the filesystem (it’s the library that does all the reading), but it’s certainly theoretically possible to, as you say, to “trick” the library into playing a file/displaying an image that doesn’t belong to it. A smart library will reject arbitrary files, so you couldn’t play /bin/ls as a sound file, but still, I think it’s completely reasonable to require that resources be located in the same directory as the game file. That should be just fine for the current use-case of Hugo and Adrift games.

Difficulty level: the Glk library doesn’t actually have inherent knowledge of where the game is located. For example, it’s perfectly legal for an interpreter to pop up an “open file” dialog if no filename is provided, so the library couldn’t even look at argv… there are, at least, the non-standard glkunix_set_base_file /sglk_set_basename which, if they’re not called, a library could reject any requests to add resources from a file, or perhaps assume the current directory is the resource directory.

I’ll look at getting the Gargoyle changes updated to confine resources this way.

Having such an extension would conflict with Hugo and Scare, yes. These have to be arbitrary filenames.

2 Likes

These resource files are often the storyfile itself! We figured that one generic function would be simpler than having distinct functions for loading from the storyfile and additional resource files.

TADS isn’t making use of this API yet, but it would be good to eventually add that too.

Pinging @Angstsmurf as you’re probably the only other person who’d be likely to implement this.

1 Like