Z-machine: parameters for @save

A big hitch in getting Bot Mode working in Dumb Frotz is how to reliably save the game after every move. I can do this now by injecting a SAVE command into the input stream. I don’t like this approach because it’s kludgy and is broken by non-English games and games for which saves are done some other way. So, I want instead to do a save by calling save_quetzal() directly. This results in a corrupted save because the arguments to @save are all wrong. The big question now is “Where do those arguments come from?”. In the Standard Library, we have @save -> flag;. That calls the @save opcode and saves the result in flag. No parameters are passed there. Does the compiler do something special to fill in the parameters? How can I get at these parameters from outside of the Z-machine?

I’m working on the problem here: https://gitlab.com/DavidGriffith/frotz/-/issues/207

In Z5 and later, @save is a VAR opcode, so the second byte of the instruction specifies the types of all its operands (which can be “word immediate”, “byte immediate”, “variable”, or “omitted”). It’s legal to omit all the operands, which is what the standard library does here.

I’m not sure how exactly you’re injecting the save instruction, but in a compiled game file, I think it should be 0x BE 00 FF 02: BE means extended form, 00 means extended opcode zero (@save), FF means “no operands provided” (0b 11 11 11 11, “omitted” four times), and 02 is the branch to take if we’re restoring from this point (“don’t branch at all”).

I’m injecting the literal word “save” into the input stream. It’s exactly as if I type the word “save” into the game. I’m trying to get a save to work by calling save_quetzal(), which in my test code is in save_frotz() so I can do any needed data-massaging there.

I think I’m missing something, then—why do the opcode parameters matter?

Once the game calls @save with no parameters, everything else (prompting for a filename etc) is up to the interpreter.

I looked at z_save() in fastmem.c again and I see that the parameters for game saves don’t matter. Still poking around at identifying just what’s going wrong.

In ZVM I run the save function after requesting line input. It doesn’t actually matter that you’re not saving after a @save instruction, you just store and restore the correct PC. But you do need to also save the parameters to @read outside the Quetzal file, or perhaps in a custom chunk (along with UI state, and the random seed).

1 Like

If you insert the implicit save just before requesting input, couldn’t you just point at the @read opcode? That way, you wouldn’t have have to give special treatment to the read parameters. You’d still have to distinguish this from a normal EXT-save to prevent the interpreter from storing the result when coming back from the save, but presumably you’re doing that already.

1 Like

Ah, yeah that would probably work too. ZVM uses Glk, so it hands control over to Glk, and then Glk asks it to make the autosave, so that’s why it happens after @read. You can probably do it at any time though, you’d just have to make it not write the result.

1 Like

I’m not sure what you’re suggesting. The fact that calling save_quetzal() outside of z_save() fails suggests to me that something else is wrong.

That was intended as a reply to Dannii. Sorry for the confusion!

Ah… I’m still confused as hell…

I don’t know specifically what Frotz’s code is doing, but you should be able to make it so that the save and restore functions will replicate the callstack and PC, regardless what instruction the PC points to. And from a brief cursory look at save_quetzal(), it does look like it should be doing that already.

Seems to be working fine (after making an initial save file):

$ ./dfrotz -B "" -L xyzzy -R . -m 905.z5 
LOAD_BOT mode set: xyzzy
Where is xyzzy?

Fatal error: Error reading save file for restore mode
$ ./dfrotz -m 905.z5 
Using normal formatting.
Loading 905.z5.
The phone rings.

Oh, no - how long have you been asleep? Sure, it was a tough night, but...
This is bad. This is very bad.

The phone rings.

  -----
 by Adam Cadre
v1.12 (2016.0430) / Original release 2000.0103
  -----

 (in bed)
This bedroom is extremely spare, with dirty laundry scattered haphazardly
all over the floor. Cleaner clothing can be found in the dresser. A
bathroom lies to the south, while a door to the east leads to the living
room.

On the end table are a telephone, a wallet and some keys.



The phone rings.

>save
Please enter a filename [905.qzl]: xyzzy


Ok.

>quit


Are you sure you want to quit? y
$ ./dfrotz -B "pick up phone" -L xyzzy -R . -m 905.z5 
LOAD_BOT mode set: xyzzy
Bot mode: BOT_LOAD     loading
>pick up phone


You pick up the phone. "Hadley!" a shrill voice cries. "Hadley, haven't you
even  yet?? You  that our presentation was at nine o'clock sharp!
First the thing with the printers, now this - there won't even be enough
 of you for Bowman to fire once he's done with you. Now get the hell
 here!!"

Click.

Bot mode: BOT_NORMAL   game progressing
pc: 97, sp: 5824
zargs[0]: 5701, zargs[1]: 5824, zargs[2]: 0
$ ./dfrotz -B "get up" -L xyzzy -R . -m 905.z5 LOAD_BOT mode set: xyzzy
Bot mode: BOT_LOAD     loading
>get up


You get out of bed.


This bedroom is extremely spare, with dirty laundry scattered haphazardly
all over the floor. Cleaner clothing can be found in the dresser. A
bathroom lies to the south, while a door to the east leads to the living
room.

On the end table are a telephone, a wallet and some keys.

Bot mode: BOT_NORMAL   game progressing
pc: 97, sp: 5824
zargs[0]: 5701, zargs[1]: 5824, zargs[2]: 0
$ ./dfrotz -B "s" -L xyzzy -R . -m 905.z5 
LOAD_BOT mode set: xyzzy
Bot mode: BOT_LOAD     loading
>s



This is a far from luxurious but still quite functional bathroom, with a
sink, toilet and shower. The bedroom lies to the north.

Bot mode: BOT_NORMAL   game progressing
pc: 97, sp: 5824
zargs[0]: 5701, zargs[1]: 5824, zargs[2]: 0

Yeah, when I was adding autosave to Fizmo, I did the work in the @read opcode.

(This wasn’t exactly the same as autosave in Glulxe. Same result, though – the interpreter performs a save before every input.)

(Okay, actually Fizmo only autosaves before line input. This was adequate for my needs.)

So it does. Try zork1.z3. Clearly the problem involves something that’s okay for V5, but not V3.

I tried with Sherlock and got this, which seems to indicate that a pointer is going off into La La Land:

Click to expand! ``` $ ./dfrotz -B "knock on door" -L sherlock.qzl -R . -m sherlock.z5 LOAD_BOT mode set: sherlock.qzl Bot mode: BOT_LOAD loading >knock on door R

You hear a bolt sliding back and the door opens to reveal a worried Mrs
Hudson.

“Oh Doctor Watson, I’m so glad to see you. It’s Mr Holmes, sir. Three days
have come and gone since he’s been out of his rooms. He won’t eat and I
know he hasn’t slept. Now there’s a visitor who says he’s here about some
important government business. I tried to send him away, but he insisted on
waiting in the parlour. That’s when I sent for you. Thank God you’ve come.”

You step inside and Mrs Hudson closes the door behind you. The entry hall
is just as you remember it - small, underheated, and lit by a feeble gas
light. The door to the parlour is to the north, and a flight of well-worn
stairs leads up to the first floor.

Bot mode: BOT_NORMAL game progressing
pc: 53, sp: 0
zargs[0]: 15246, zargs[1]: 14532, zargs[2]: 0
thoth:~/proj/int-fiction/frotz/frotz-git$ ./dfrotz -B “up” -L sherlock.qzl -R . -m sherlock.z5
LOAD_BOT mode set: sherlock.qzl
Bot mode: BOT_LOAD loading

up
Whr?

You climb the stairs and pause outside the door to Holmes’s study, nervous
about what you might find inside.

Holmes’s study is to the north.

Bot mode: BOT_NORMAL game progressing
pc: 53, sp: 0
zargs[0]: 15246, zargs[1]: 14532, zargs[2]: 0
thoth:~/proj/int-fiction/frotz/frotz-git$ ./dfrotz -B “north” -L sherlock.qzl -R . -m sherlock.z5
LOAD_BOT mode set: sherlock.qzl
Bot mode: BOT_LOAD loading

north
Y

You step back through time to your bachelor days, when you shared these
cluttered digs with the world’s only consulting detective. Despite the
mess, you can see that the room is unchanged. The fireplace to the north
has a pile of letters transfixed to its mantelpiece by a jackknife. Next to
them lies Holmes’s six-shot revolver, the tobacco slipper, and Holmes’s
favorite pipe. The violin has been carelessly tossed onto a stack of
obviously unread newspapers.

Holmes is slouched on the sofa, lost in a black mood of despair. He is pale
and emaciated, and his sharp eyes burn with fever. He is staring at a phial
that he holds in one hand. His free hand rests near a hypodermic syringe in
a neat morocco case at his side. You realize that he is fighting a
depression brought on by what he calls “the dull routine of existence.”

The bedroom door to the west is closed. The only other exit is the door to
the south.

Bot mode: BOT_NORMAL game progressing
pc: 53, sp: 0
zargs[0]: 15246, zargs[1]: 14532, zargs[2]: 0

</details>

Could the problem be that the restore code is making an assumption that is no longer true, say that the pc points to a save instruction? It is too late for me to check, maybe tomorrow.

That sounds reasonable.

Does anyone else have any ideas? I’ve been fiddling with this for several months now.

The following hack allows me to play (at least) a few moves with both zork1.z3 and 905.z5:

diff --git a/src/common/fastmem.c b/src/common/fastmem.c
index 36a4f6b..7eb3370 100644
--- a/src/common/fastmem.c
+++ b/src/common/fastmem.c
@@ -64,7 +64,7 @@ char auxilary_name[MAX_FILE_NAME + 1] = DEFAULT_AUXILARY_NAME;
 zbyte huge *zmp = NULL;
 zbyte huge *pcp = NULL;
 
-static FILE *story_fp = NULL;
+FILE *story_fp = NULL;
 
 /*
  * Data for the undo mechanism.
diff --git a/src/common/process.c b/src/common/process.c
index d62c543..a87ab8b 100644
--- a/src/common/process.c
+++ b/src/common/process.c
@@ -242,11 +242,16 @@ static void load_all_operands(zbyte specifier)
  * Z-code interpreter main loop
  *
  */
+extern FILE *story_fp;
+
 void interpret(void)
 {
        /* If we got a save file on the command line, use it now. */
        if (f_setup.restore_mode == 1) {
-               z_restore();
+               FILE *gfp = fopen(f_setup.save_name, "rb");
+               if (!gfp) printf("%s not found\n", f_setup.save_name),exit(0); 
+               restore_quetzal(gfp, story_fp);
+               fclose (gfp);
                f_setup.restore_mode = 0;
        }
 

This results in an Illegal opcode error straight off.