Illegal use of parameters in opcode get_wind_prop (Zork0_242)

@Angstsmurf posted an interesting issue for my z-machine disassembler UnZ

"
There are two binaries of Zork Zero with release number 242 available at the Obsessively Complete Infocom Catalog: zork0-beta-r242-s880830.z6 and zork0-r242-s880901.z6.

With both of these, Unz fails to decode the routine at 0x1cc28 (referring to it as [Invalid routine: 0x1CC28].)
Fortunately, we have the ZIL source for this version, including this routine:

<CONSTANT WTBL <LTABLE 0>>

<ROUTINE WINPROP (WIN PROP)
	 <WINGET .WIN ,WTBL .PROP>
	 <GET ,WTBL 1>>

This code is still present in later sources, but commented out. It would seem that it targets a preliminary version of the v6 Z-machine, where WINGET takes an address to a table in addition to the property number. The WINPROP routine basically converts it to the later syntax.

Here is the output of Unz at address 0x1cc28:

Data/orphan fragment:
1CC20                         02 BE 13 8B 01 70 8B 02          .....p..
1CC30 CF 1F 70 8B 01 00 B8 00                          ..p.....

"

I’ve added the following comment to my code:

        ' zork0-beta-r242-s880830.z6 and zork0-r242-s880901.z6 contains code that uses 3 parameters for WINGET.
        ' Source code:
        '
        '   <CONSTANT WTBL <LTABLE 0>>
        '       <ROUTINE WINPROP(WIN PROP)
        '       <WINGET .WIN ,WTBL .PROP>
        '       <GET ,WTBL 1>>
        '
        ' Compiles to 02 BE 13 8B 01 70 8B 02 CF 1F 70 8B 01 00 B8 00 at address 0x1cc28.
        '
        ' This is outside the standards of the z-machine and probably only was legel during development
        ' of z6 in an unreleased compiler.
        ' For completeness unz can disassemble it even though probably no interpreter can run it.

And in the new, yet unreleased, version this will decode as:

Inform syntax:

Routine: 0x1CC28               Called from routine(s) at 0x0F254, 0x1C1B8, 0x1C264, 0x1CC38, 0x2ECEC, 0x31070, 0x310D4, 0x31284,
                                                         0x312E0
1CC28 02                       2 locals
                               (local0 local1)

1CC29 02 13 8B 01 70 8B 02     get_wind_prop local0 0x708b local1 "*** ILLEGAL SET OF PARAMETERS FOR THIS OPCODE, ONLY USED IN ZORK0 DURING DEVELOPMENT ***"
1CC30 CF 1F 70 8B 01 00        loadw 0x708b 0x01 -> sp
1CC36 B8                       ret_popped

Padding:
1CC37 00

ZAP syntax:

Routine: 0x1CC28               Called from routine(s) at 0x0F254, 0x1C1B8, 0x1C264, 0x1CC38, 0x2ECEC, 0x31070, 0x310D4, 0x31284,
                                                         0x312E0
1CC28 02                       2 locals
                               (L0 L1)

1CC29 02 13 8B 01 70 8B 02     WINGET L0,28811,L1 "*** ILLEGAL SET OF PARAMETERS FOR THIS OPCODE, ONLY USED IN ZORK0 DURING DEVELOPMENT ***"
1CC30 CF 1F 70 8B 01 00        GET 28811,1 >STACK
1CC36 B8                       RSTACK

Padding:
1CC37 00

This may be interesting to add as a note in the revision of the z-machine standards document and developer of other disassembly tools (@cas, for zd maybe?).

5 Likes

Excellent work!

I wonder how hard it would be to hack Frotz to run this version of Zork Zero.

Probably not that hard. The opcode is correctly formatted as an EXTOP-operand with the correct parameter definition (8B = 10 00 10 11 = variable, long immediate, variable, no more operands).

WINGET only needs to store the result in the supplied address, instead of the expected storage variable.

Definition ZAP:
WINGET window,offset >VAL

Definition Inform:
EXT:19 13 6 get_wind_prop window property-number → (result)

1 Like

The relevant part is in screen.c

/*
 * z_get_wind_prop, store the value of a window property.
 *
 *	zargs[0] = window (-3 is the current one)
 *	zargs[1] = number of window property to be stored
 *
 */
void z_get_wind_prop(void)
{
	flush_buffer();

	if (zargs[1] < 16)
		store(((zword *) (wp + winarg0()))[zargs[1]]);
	else if (zargs[1] == 16)
		store (os_to_true_colour(lo (wp [winarg0 ()].colour)));
	else if (zargs[1] == 17) {
		zword bg = hi (wp [winarg0 ()].colour);
		if (bg == TRANSPARENT_COLOUR)
			store ((zword) -4);
		else
			store (os_to_true_colour (bg));
	} else
		runtime_error(ERR_ILL_WIN_PROP);

} /* z_get_wind_prop */

I guess you could test if zargc is 3 and ther if it is store the value with storew instead, at the address supplied with zargs[1].

(I can’t test this hack tonight, but maybe tomorrow when I’m back at the right computer.)

2 Likes

Very cool.

Interesting relic, sadly I haven’t much time to delve into it…

Best regards from Italy,
dott. Piergiorgio.

I got it to run in Frotz with your suggested changes, but it has other problems. Apparently this is not the only difference from the final v6 Z-machine.

I was just about to post something similiar. I did theses changes in screen.c:

void z_get_wind_prop(void)
{
	flush_buffer();

	if (zargc == 3) {	/* Hack for Zork0_r242 */
		if (zargs[2] < 16)
			storew((zword) (zargs[1] + 2), ((zword *) (wp + winarg0()))[zargs[2]]);
		else if (zargs[2] == 16)
			storew ((zword) (zargs[1] + 2), os_to_true_colour(lo (wp [winarg0 ()].colour)));
		else if (zargs[2] == 17) {
			zword bg = hi (wp [winarg0 ()].colour);
			if (bg == TRANSPARENT_COLOUR)
				storew ((zword) (zargs[1] + 2), (zword) -4);
			else
				storew ((zword) (zargs[1] + 2), os_to_true_colour (bg));
		} else
			runtime_error(ERR_ILL_WIN_PROP);
	} else {
		if (zargs[1] < 16)
			store(((zword *) (wp + winarg0()))[zargs[1]]);
		else if (zargs[1] == 16)
			store (os_to_true_colour(lo (wp [winarg0 ()].colour)));
		else if (zargs[1] == 17) {
			zword bg = hi (wp [winarg0 ()].colour);
			if (bg == TRANSPARENT_COLOUR)
				store ((zword) -4);
			else
				store (os_to_true_colour (bg));
		} else
			runtime_error(ERR_ILL_WIN_PROP);
	}
} /* z_get_wind_prop */

And still got error:


But then I noticed that WINPUT also is used slightly different.

pic.zil (r242):

		<PUT ,WIN-TBL 0 2>
		<PUT ,WIN-TBL 1 ,RESET-MARGIN>
		<PUT ,WIN-TBL 2 .Y ;<+ .Y 1>>
		<WINPUT 0 ,WIN-TBL ,WCRFUNC>)>>

pic.zil (r393):

		<WINPUT 0 ,WCRCNT .Y>
		<WINPUT 0 ,WCRFUNC ,RESET-MARGIN>)>>

Notice that the parameters shifted around.

Just ignoring the WINPUT-function for the moment, changing z_put_wind_prop to:

void z_put_wind_prop(void)
{
	flush_buffer();
	
	return;
	
	if (zargs[1] >= 16)
		runtime_error(ERR_ILL_WIN_PROP);

	((zword *) (wp + winarg0()))[zargs[1]] = zargs[2];
} /* z_put_wind_prop */

Is partially sucessful (I use Unix Frotz, so don’t mind the missing graphics):

1 Like

Thanks! Presumably the ignored z_put_wind_prop() calls is what messes up the text margins. Or it might not like the graphics files from release 393.

This is a hacked z_put_wind_prop() that works on Unix Frotz and don’t ignore the values, if you want to try it…

void z_put_wind_prop(void)
{
	flush_buffer();
	
	if (zargs[1] >= 16) { /* Hack for Zork0_r242 */
		if (zargs[2] >= 16)
			runtime_error(ERR_ILL_WIN_PROP);

		zword cnt, i, value;
		LOW_WORD(zargs[1], cnt)		/* Get number of values */
		for (i = 0; i < cnt; i++) {
			LOW_WORD(zargs[1] + (i + 1) * 2, value)
			((zword *) (wp + winarg0()))[zargs[2] + i] = value;
		}
	} else
		((zword *) (wp + winarg0()))[zargs[1]] = zargs[2];
} /* z_put_wind_prop */

(You noticed the (zargs[1] + 2) in z_get_wind_prop above? That is because the first word in a LTABLE is the length of the table and the actual values start in the next slot.)

Success!

2 Likes

I’m gonna make a ticket of this on both Frotz and WindowsFrotz so it’s documented. Then it’s up to the maintainers to decide if they want to support version 242, or not.

I went over the fix suggested at https://gitlab.com/DavidGriffith/frotz/-/issues/297. It seems to work fine. @heasm66, is your suggested fix still “hacky” or do you consider it finalized?

I went over it pretty quick, and it’s untested. I left the same comment on the fork WindowsFrotz, and @DavidK suggested/planning to implement it only for these two binaries.

Maybe implement similiary on both?

I added a check to see if Zork Zero with release number 242 is being played and if so, use the fix.

I’ve locally modified zd to support this, and it’ll probably be in the next zdevtools release.

I also added support in Bocfel, though via patching rather than updating the implementation of @get_wind_prop. Since all calls helpfully go through the WINPROP routine, it’s trivial to patch the single call to WINGET:

At offset 0x1cc2b (for both versions), update 5 bytes there to af 01 02 00 b8 which hijacks the existing @get_wind_prop, passes L00 and L01 to it, and pushes the result onto the stack; then it calls @ret_popped.

At this point the game’s pretty unplayable in Bocfel due to no graphics support for non-official games, and just general jankiness of untested version 6 games, but at least it doesn’t abort…