I’m really missing something out here. What are the limitations with creating and writing to new files during runtime in ZIL and the z-machine in general? I seem to find things stating only glulx can do that, and then I see others saying against that. Can someone guide me down that route (opcodes or just general can/cannot)?
It’s a rarely used part of the Z Machine spec (so rare that no one’s ever asked me to add support to Parchment), and I don’t there’s really any way to test if it’s supported by the interpreter either.
The advice is: if you need data files, just use Glulx or another format.
Huh. Thanks. So it’ssort of possible, but not really…
Well unfortunately, I’m using ZIL. So that’s not possible… that’s why the question arise.
You can check the header to see if the terp claims to adhere fully to standard 1.1 or above. If it does, you should be able to use this feature. If not, you can either not use it, or hope the interpreter supports it, or ask the user whether to use this feature.
Yeah, except ZVM says it’s 1.1 despite not supporting the data files part of
@save… which is part of the 1.0 spec anyway. Maybe I shouldn’t say it’s a 1.1 terp, but I’d prefer people can test for the true colour opcodes…
I probably should just add support for it. It wouldn’t be too complex, much simpler than timer interrupts at least. Not that it can be implemented in totality with Glk - you can either create a file with a specified filename, or ask the user for a filename, but you can’t suggest a filename for the user to then adjust.
The z-machine 1.0 standard allows for the SAVE opcode to have extra arguments allowing it to save an area of memory into a file with a suggested file name. The 1.1 standard added an additional optional argument to indicate whether the interpreter should prompt the user, or silently save using the suggested default name. This optional parameter presents a theoretical security risk for people running untrusted story files. It’s implementation should either be skipped entirely, or undertaken very carefully by interpreters.
Edit: I have no idea which, if any, interpreters support this feature, or which games use it.
A standard interpreter which doesn’t support external files should still recognise attempts to save and restore external files, but return 0 to indicate failure in both cases.
I created a small Inform 6 program to test interpreter support for external files.
The game file is at http://frobnitz.co.uk/zmachine/externalfiles.z5 and the source at http://frobnitz.co.uk/zmachine/externalfiles.inf
I tested it on Fabularium, and it didn’t function properly (prompted for save). I’ve already sent an email to the developer, though the last update for Fabularium was in 2018, so who knows?
EDIT: Also added a problem to Hunky Punk’s GitHub page.
There’s a small bug: the filename is effectively empty (per the standard, “name is […] preceded by a byte giving the number of characters”). It should be:
Array fname -> 4 't' 'e' 's' 't';
The latest Gargoyle does support external files like this. With the array update, this will read/write from the specified file, without prompting, although it stores it in a per-game directory so that games can’t stomp on any of the user’s file.
Gargoyle does not support prompting of the suggested filename, though, because Glk can’t do that. If you ask for it to prompt, it’ll suggest a generic filename (
You’re right, I’m not sure why I put 0 there instead of 4. The files have been updated.
Zero length names should be mentioned in the standard as being invalid.
Additionally: The standard really ought to have more to say here on the subject of filenames. It should mention that allowing unprompted saving of story-controlled files is dangerous without restrictions and it should restrict what byte values are legal in suggested names.
While I’ve not tested Gargoyle specfically, this isn’t guaranteed to be safe given path traversal attacks like “…/…/…/…/…/…/…/…/…/…/important_dir/important_file”. That isn’t the only possible attack either.
I’d suggest limiting filenames to ASCII alphanumeric only, with the possible addition of hyphen and underscore. All other byte values should be invalid and stripped from the suggested name, or result in an error with no chance of saving.
Section 126.96.36.199 on the version of the Z-Machine Standard on Github reads
The interpreter should delete from the filename any characters illegal for a filename. This will include all of the following characters (and more, if the OS requires it): slash, backslash, angle brackets (less-than and greater-than), colon, double-quote, pipe (vertical bar), question-mark, asterisk. The library should also truncate the argument at the first full stop (delete the first full stop and any following characters). If the result is the empty string, change it to the string “NULL”.
Actually, the version on github has two sections 188.8.131.52, probably as a result of unfinished editing.
Gargoyle (or Bocfel, rather) does indeed ensure that directory traversal attacks aren’t possible.
The standard does say “[t]he interpreter should delete from the filename any characters illegal for a filename”, and gives some specific characters, although on Unix systems, at least, all thses characters are legal. Bocfel is fairly lax and allows all characters apart from slash, but probably should be updated to screen out the listed characters, even if they’re legal.
The standard is also somewhat contradictory here:
§184.108.40.206: “The interpreter must convert all filenames to upper case before use. If no full stop is given, “.AUX” should be appended.”
§220.127.116.11: “The library should also truncate the argument at the first full stop (delete the first full stop and any following characters).”
First, is “the library” really “the interpreter”? But more importantly, an extension must be present per 18.104.22.168, and then removed per 22.214.171.124.
And while I’m looking at it, §7.6.1 says “Filenames have the following format (approximately the MS-DOS 8.3 rule): one to eight alphanumeric characters, a full stop and zero to three alphanumeric characters (the “file extension”).”.
If that’s true, then the list of characters to remove is fixed, i.e. all non alphanumeric characters apart from a single full stop. But the standard really is silent on what happens if the filename doesn’t match the requirement here.
I’m really not sure how much it matters, in the end, though. These files likely won’t be shared between interpreters, so all that really matters is that an interpreter can load a file that it previously saved, I think.
The ‘library’ is the interpreter, yes. The text of §126.96.36.199: is copied from, I think, Glk. This section was a later addition to the Standard, to try to avoid some of the problems inherent with games being allowed to create files at will without notification.
I’ve put together a proposed rewrite over in the thread for updating the z-machine standards document.
Argh, I forgot this existed in the standard. Still, it needs work.
I’ll take your word for it, but there is more to it than just filtering “…” or “/”, so I do think the standard should be more clear about it than it is.
Edit: No idea why the forum insists on changing double dots to triple dots.
Very curious question: how difficult would it be to add hidden prompting to Parchment? Because a game I’m doing relies … very very heavily on it (this might seem weird, but a per-turn basis, so any freak quits (not QUITting, but eg. Closing tab) are accounted for). There is nowhere near a rush, but simply the wondering, in case I ever finish this project.
Probably not too difficult, but I haven’t looked at that part of the ZVM code in years, and it would take some time to remember how it works. (It’s a little bit tricky because the code for
@save is split in two because it has to stop, get the filename from the player, and then resume, and async JS wasn’t an option back then.)
I am planning a major refresh/rewrite of ZVM this year, so if I don’t update the current ZVM, I’ll make sure to include it in the next version.