├── .gitignore
├── zork1.z3
├── Procfile
├── .openshift
├── settings.xml
├── mvn
└── action_hooks
│ ├── build
│ ├── stop
│ └── start
├── src
└── main
│ ├── java
│ └── com
│ │ ├── stormpath
│ │ └── ozorkauth
│ │ │ ├── model
│ │ │ ├── CommandRequest.java
│ │ │ ├── Registration.java
│ │ │ └── CommandResponse.java
│ │ │ ├── Application.java
│ │ │ ├── config
│ │ │ └── SpringSecurityWebAppConfig.java
│ │ │ ├── support
│ │ │ └── ZMachinery.java
│ │ │ ├── service
│ │ │ └── GameService.java
│ │ │ └── controller
│ │ │ └── GameController.java
│ │ └── zaxsoft
│ │ ├── zmachine
│ │ ├── Monitor.java
│ │ ├── ZCallFrame.java
│ │ ├── ZRandom.java
│ │ ├── ZMemory.java
│ │ ├── ZUserInterface.java
│ │ ├── ZIOCard.java
│ │ ├── ZObjectTable.java
│ │ └── ZCPU.java
│ │ ├── LICENSE.txt
│ │ └── README.txt
│ └── resources
│ ├── logback.xml
│ └── application.properties
├── app.json
├── ozorkauth-cli
├── pom.xml
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | target
3 | *.iml
4 |
--------------------------------------------------------------------------------
/zork1.z3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dogeared/OZorkAuth/HEAD/zork1.z3
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: java $JAVA_OPTS -Dserver.port=$PORT -jar target/*.jar
2 |
--------------------------------------------------------------------------------
/.openshift/settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ${OPENSHIFT_DEPENDENCIES_DIR}
4 |
5 |
--------------------------------------------------------------------------------
/.openshift/mvn:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | prog=$(basename $0)
3 | export JAVACMD=$JAVA_HOME/bin/java
4 | export M2_HOME=/usr/share/java/apache-maven-3.0.4
5 | exec $M2_HOME/bin/$prog "$@"
6 |
--------------------------------------------------------------------------------
/.openshift/action_hooks/build:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | export JAVA_HOME=/etc/alternatives/java_sdk_1.8.0
4 | export PATH=$JAVA_HOME/bin:$PATH
5 | cd $OPENSHIFT_REPO_DIR
6 | ./.openshift/mvn clean package -s .openshift/settings.xml -DskipTests=true
7 |
--------------------------------------------------------------------------------
/.openshift/action_hooks/stop:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | source $OPENSHIFT_CARTRIDGE_SDK_BASH
3 | PID=$(ps -ef | grep java.*\.jar | grep -v grep | awk '{ print $2 }')
4 | if [ -z "$PID" ]
5 | then
6 | client_result "Application is already stopped"
7 | else
8 | kill $PID
9 | fi
10 |
--------------------------------------------------------------------------------
/.openshift/action_hooks/start:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | export JAVA_HOME=/etc/alternatives/java_sdk_1.8.0
4 | export PATH=$JAVA_HOME/bin:$PATH
5 | export ZMACHINE_FILE=${OPENSHIFT_REPO_DIR}zork1.z3
6 | cd $OPENSHIFT_REPO_DIR
7 | nohup java -jar target/*.jar --server.port=${OPENSHIFT_DIY_PORT} --server.address=${OPENSHIFT_DIY_IP} &
8 |
--------------------------------------------------------------------------------
/src/main/java/com/stormpath/ozorkauth/model/CommandRequest.java:
--------------------------------------------------------------------------------
1 | package com.stormpath.ozorkauth.model;
2 |
3 | public class CommandRequest {
4 |
5 | private String request;
6 |
7 | public String getRequest() {
8 | return request;
9 | }
10 |
11 | public void setRequest(String request) {
12 | this.request = request;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "OZorkAuth",
3 | "description": "Play the classic text-based game - Zork I - using standard OAuth2 authorization flows.",
4 | "repository": "https://github.com/dogeared/OZorkAuth/tree/master",
5 | "keywords": ["spring boot", "identity", "security", "authentication"],
6 | "success_url": "/",
7 | "env": {
8 | "STORMPATH_API_KEY_ID": {
9 | "description": "The API key ID from your ~/.stormpath/apiKey.properties file",
10 | "value": "changeme"
11 | },
12 | "STORMPATH_API_KEY_SECRET": {
13 | "description": "The API key secret from your ~/.stormpath/apiKey.properties file",
14 | "value": "changeme"
15 | },
16 | "STORMPATH_APPLICATION_HREF": {
17 | "description": "The URL for your Stormpath Application from the Stormpath Admin Dashboard",
18 | "value": "changeme"
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/main/java/com/stormpath/ozorkauth/model/Registration.java:
--------------------------------------------------------------------------------
1 | package com.stormpath.ozorkauth.model;
2 |
3 | public class Registration {
4 |
5 | private String givenName;
6 | private String surName;
7 | private String email;
8 | private String password;
9 |
10 | public String getGivenName() {
11 | return givenName;
12 | }
13 |
14 | public void setGivenName(String givenName) {
15 | this.givenName = givenName;
16 | }
17 |
18 | public String getSurName() {
19 | return surName;
20 | }
21 |
22 | public void setSurName(String surName) {
23 | this.surName = surName;
24 | }
25 |
26 | public String getEmail() {
27 | return email;
28 | }
29 |
30 | public void setEmail(String email) {
31 | this.email = email;
32 | }
33 |
34 | public String getPassword() {
35 | return password;
36 | }
37 |
38 | public void setPassword(String password) {
39 | this.password = password;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxsoft/zmachine/Monitor.java:
--------------------------------------------------------------------------------
1 | package com.zaxsoft.zmachine;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | public class Monitor {
7 |
8 | private static final Logger log = LoggerFactory.getLogger(Monitor.class);
9 |
10 | private final Object myMonitorObject = new Object();
11 | private boolean wasSignalled = false;
12 |
13 | public void doWait() {
14 | synchronized (myMonitorObject) {
15 | while (!wasSignalled) {
16 | try {
17 | myMonitorObject.wait();
18 | } catch(InterruptedException e){
19 | log.error("Interrupted while waiting", e);
20 | }
21 | }
22 | //clear signal and continue running.
23 | wasSignalled = false;
24 | }
25 | }
26 |
27 | public void doNotify() {
28 | synchronized (myMonitorObject) {
29 | wasSignalled = true;
30 | myMonitorObject.notify();
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/src/main/java/com/stormpath/ozorkauth/Application.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Stormpath, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.stormpath.ozorkauth;
17 |
18 | import org.springframework.boot.SpringApplication;
19 | import org.springframework.boot.autoconfigure.SpringBootApplication;
20 |
21 | @SpringBootApplication
22 | public class Application {
23 | public static void main(String[] args) {
24 | SpringApplication.run(Application.class, args);
25 | }
26 | }
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2015 Stormpath, Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | # If you have more than one application registered with Stormpath, you must specify which one
18 | # corresponds to this webapp:
19 | #
20 | #stormpath.application.href = https://api.stormpath.com/v1/applications/YOUR_APPLICATION_ID_HERE
21 |
22 | server.tomcat.remote_ip_header = x-forwarded-for
23 | server.tomcat.protocol_header = x-forwarded-proto
24 |
25 | stormpath.web.oauth2.uri = /v1/a
26 | stormpath.web.login.enabled = false
--------------------------------------------------------------------------------
/src/main/java/com/zaxsoft/LICENSE.txt:
--------------------------------------------------------------------------------
1 | As of version 0.91, Zax is licensed under the MIT license. Legal language follows:
2 |
3 | Copyright (c) 2008 Matthew E. Kimmel
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/main/java/com/stormpath/ozorkauth/model/CommandResponse.java:
--------------------------------------------------------------------------------
1 | package com.stormpath.ozorkauth.model;
2 |
3 | import com.fasterxml.jackson.annotation.JsonInclude;
4 | import com.fasterxml.jackson.annotation.JsonInclude.Include;
5 |
6 | @JsonInclude(Include.NON_NULL)
7 | public class CommandResponse {
8 | private String[] gameInfo;
9 | private String request;
10 | private String[] response;
11 | private String[] look;
12 | private String status;
13 | private String message;
14 |
15 | public String[] getGameInfo() {
16 | return gameInfo;
17 | }
18 |
19 | public void setGameInfo(String[] gameInfo) {
20 | this.gameInfo = gameInfo;
21 | }
22 |
23 | public String getRequest() {
24 | return request;
25 | }
26 |
27 | public void setRequest(String request) {
28 | this.request = request;
29 | }
30 |
31 | public String[] getResponse() {
32 | return response;
33 | }
34 |
35 | public void setResponse(String[] response) {
36 | this.response = response;
37 | }
38 |
39 | public String[] getLook() {
40 | return look;
41 | }
42 |
43 | public void setLook(String[] look) {
44 | this.look = look;
45 | }
46 |
47 | public String getStatus() {
48 | return status;
49 | }
50 |
51 | public void setStatus(String status) {
52 | this.status = status;
53 | }
54 |
55 | public String getMessage() {
56 | return message;
57 | }
58 |
59 | public void setMessage(String message) {
60 | this.message = message;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/com/stormpath/ozorkauth/config/SpringSecurityWebAppConfig.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Stormpath, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.stormpath.ozorkauth.config;
17 |
18 | import org.springframework.context.annotation.Configuration;
19 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
20 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
21 |
22 | import static com.stormpath.spring.config.StormpathWebSecurityConfigurer.stormpath;
23 |
24 | @Configuration
25 | public class SpringSecurityWebAppConfig extends WebSecurityConfigurerAdapter {
26 | @Override
27 | protected void configure(HttpSecurity http) throws Exception {
28 | http
29 | .apply(stormpath()).and()
30 | .authorizeRequests()
31 | .antMatchers("/").permitAll()
32 | .antMatchers("/v1/instructions").permitAll()
33 | .antMatchers("/v1/r").permitAll().and()
34 | .csrf().ignoringAntMatchers("/v1/c").and()
35 | .csrf().ignoringAntMatchers("/v1/r");
36 | }
37 | }
--------------------------------------------------------------------------------
/src/main/java/com/zaxsoft/zmachine/ZCallFrame.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2008 Matthew E. Kimmel
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in
12 | * all copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | * THE SOFTWARE.
21 | */
22 | package com.zaxsoft.zmachine;
23 |
24 | import java.util.Stack;
25 |
26 | /**
27 | * A frame on the ZMachine's call stack.
28 | *
29 | * @author Matt Kimmel
30 | */
31 | class ZCallFrame {
32 | // Constants used with calltype;
33 | static final int FUNCTION = 0;
34 | static final int PROCEDURE = 1;
35 | static final int INTERRUPT = 2;
36 |
37 | // Variables
38 | int pc; // Program counter
39 | Stack routineStack; // Routine stack
40 | int[] localVars = new int[15]; // Local variables
41 | int numLocalVars; // Number of local variables
42 | int callType; // How this routine was called
43 | int argCount; // Argument count
44 | int frameNumber; // Used in CATCH and THROW. First frame is 0, increases from there.
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxsoft/zmachine/ZRandom.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2008 Matthew E. Kimmel
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in
12 | * all copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | * THE SOFTWARE.
21 | */
22 | package com.zaxsoft.zmachine;
23 |
24 | import java.util.Random;
25 |
26 | class ZRandom extends Object {
27 | private ZUserInterface zui;
28 | private Random rnd;
29 |
30 | // The initialize function performs necessary initialization
31 | // (if any). It is passed the ZUserInterface object for this
32 | // ZMachine.
33 | void initialize(ZUserInterface ui)
34 | {
35 | zui = ui;
36 | rnd = new Random();
37 | }
38 |
39 | // Seed the random number generator with s. If s == 0, use
40 | // an outside source.
41 | void seed(int s)
42 | {
43 | if (s == 0)
44 | rnd = new Random();
45 | else
46 | rnd = new Random((long)s);
47 | }
48 |
49 | // Get a random number between 1 and s, inclusive.
50 | int getRandom(int s)
51 | {
52 | return (Math.abs(rnd.nextInt()) % s) + 1;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/ozorkauth-cli:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | ## Make sure http exists
4 | command -v http >/dev/null 2>&1 || { echo >&2 "This script requires that httpie be installed. On Mac, you can install it with: brew install httpie"; exit 1; }
5 |
6 | BASE_URL=https://ozorkauth.herokuapp.com
7 | COMMAND=
8 | EMAIL=
9 | PASSWORD=
10 | FIRST_NAME=
11 | LAST_NAME=
12 | REQUEST=
13 | TOKEN=
14 |
15 | while getopts ":b:c:f:l:e:p:r:t:" opt; do
16 | case $opt in
17 | b)
18 | BASE_URL=$OPTARG
19 | ;;
20 | c)
21 | COMMAND=$OPTARG
22 | ;;
23 | f)
24 | FIRST_NAME=$OPTARG
25 | ;;
26 | l)
27 | LAST_NAME=$OPTARG
28 | ;;
29 | e)
30 | EMAIL=$OPTARG
31 | ;;
32 | p)
33 | PASSWORD=$OPTARG
34 | ;;
35 | r)
36 | REQUEST=$OPTARG
37 | ;;
38 | t)
39 | TOKEN=$OPTARG
40 | ;;
41 | \?)
42 | echo "Invalid option: -$OPTARG" >&2
43 | exit 1
44 | ;;
45 | :)
46 | echo "Option -$OPTARG requires an argument." >&2
47 | exit 1
48 | ;;
49 | esac
50 | done
51 |
52 | if [ "$COMMAND" = "auth" ]; then
53 | OAUTH_RESPONSE=`http -f POST $BASE_URL/v1/a Origin:$BASE_URL grant_type=password username=$EMAIL password=$PASSWORD`
54 | REGEX='"access_token":"(.*)","refresh_token"'
55 | if [[ $OAUTH_RESPONSE =~ $REGEX ]]; then
56 | TOKEN=${BASH_REMATCH[1]}
57 | echo $TOKEN
58 | else
59 | echo "Something's done horribly wrong. Please check to make sure that you've supplied and email and password. You can also try hitting the endpoint directly: "
60 | echo "\thttp -f POST $BASE_URL/v1/a Origin:$BASE_URL grant_type=password username= password="
61 | fi
62 | elif [ "$COMMAND" = "game" ]; then
63 | if [ -n "$REQUEST" ]; then
64 | http POST $BASE_URL/v1/c Authorization:"Bearer $TOKEN" request="$REQUEST"
65 | else
66 | http POST $BASE_URL/v1/c Authorization:"Bearer $TOKEN"
67 | fi
68 | elif [ "$COMMAND" = "register" ]; then
69 | http POST $BASE_URL/v1/r givenName=$FIRST_NAME surName=$LAST_NAME email=$EMAIL password=$PASSWORD
70 | elif [ "$COMMAND" = "instructions" ]; then
71 | http $BASE_URL/v1/instructions
72 | else
73 | echo "Usage: "
74 | echo
75 | echo "Instructions (similar to this but delivered from the API):"
76 | echo " $0 -c instructions"
77 | echo
78 | echo "Register:"
79 | echo " $0 -c register -f -l -e -p "
80 | echo
81 | echo "Authenticate using OAuth2 and get an access token:"
82 | echo " $0 -c auth -e -p "
83 | echo
84 | echo "Play the game:"
85 | echo " $0 -c game -t -r \"\""
86 | echo " NOTE: if you leave off the -r, the response will be the result of looking around your current surroundings."
87 | echo " NOTE: The game is restored from where you last left off and is automatically saved after each command."
88 | fi
89 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | 4.0.0
20 |
21 | com.stormpath.games
22 | ozorkauth
23 | 0.2.0-SNAPSHOT
24 |
25 | OZorkAuth
26 | OAuth2 based interactive play of the classic text-based adventure: Zork
27 |
28 |
29 | UTF-8
30 | 1.1.2
31 | 1.7.8
32 | 1.3.3.RELEASE
33 | 1.1.0
34 |
35 |
36 |
37 |
38 |
39 | com.stormpath.spring
40 | stormpath-default-spring-boot-starter
41 | ${stormpath.version}
42 |
43 |
44 |
45 |
46 | org.slf4j
47 | jcl-over-slf4j
48 | ${slf4j.version}
49 | runtime
50 |
51 |
52 | ch.qos.logback
53 | logback-classic
54 | ${logback.version}
55 | runtime
56 |
57 |
58 | com.google.guava
59 | guava
60 | 19.0
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | org.apache.maven.plugins
69 | maven-compiler-plugin
70 | 3.5.1
71 |
72 | 1.8
73 | 1.8
74 |
75 |
76 |
77 | org.springframework.boot
78 | spring-boot-maven-plugin
79 | ${spring.boot.version}
80 |
81 |
82 |
83 | repackage
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxsoft/zmachine/ZMemory.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2008 Matthew E. Kimmel
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in
12 | * all copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | * THE SOFTWARE.
21 | */
22 | package com.zaxsoft.zmachine;
23 |
24 | import java.io.*;
25 |
26 | class ZMemory extends Object {
27 | private ZUserInterface zui;
28 | byte[] data;
29 | int dataLength;
30 |
31 | // The initialize routine sets things up and loads a game
32 | // into memory. It is passed the ZUserInterface object
33 | // for this ZMachine and the filename of the story-file.
34 | void initialize(ZUserInterface ui,String storyFile)
35 | {
36 | File f;
37 | FileInputStream fis;
38 | DataInputStream dis;
39 | zui = ui;
40 |
41 | // Read in the story file
42 | f = new File(storyFile);
43 | if((!f.exists()) || (!f.canRead()) || (!f.isFile()))
44 | zui.fatal("Storyfile " + storyFile + " not found.");
45 | dataLength = (int)f.length();
46 | data = new byte[dataLength];
47 | try {
48 | fis = new FileInputStream(f);
49 | dis = new DataInputStream(fis);
50 | dis.readFully(data,0,dataLength);
51 | fis.close();
52 | }
53 | catch (IOException ioex) {
54 | zui.fatal("I/O error loading storyfile.");
55 | }
56 | }
57 |
58 | // Fetch a byte from the specified address
59 | int fetchByte(int addr)
60 | {
61 | if (addr > (dataLength - 1))
62 | zui.fatal("Memory fault: address " + addr);
63 | int i = (data[addr] & 0xff);
64 | return i;
65 | }
66 |
67 | // Store a byte at the specified address
68 | void putByte(int addr,int b)
69 | {
70 | if (addr > (dataLength - 1))
71 | zui.fatal("Memory fault: address " + addr);
72 | data[addr] = (byte)(b & 0xff);
73 | }
74 |
75 | // Fetch a word from the specified address
76 | int fetchWord(int addr)
77 | {
78 | int i;
79 |
80 | if (addr > (dataLength - 1))
81 | zui.fatal("Memory fault: address " + addr);
82 | i = (((data[addr] << 8) | (data[addr+1] & 0xff)) & 0xffff);
83 | return i;
84 | }
85 |
86 | // Store a word at the specified address
87 | void putWord(int addr,int w)
88 | {
89 | if (addr > (dataLength - 1))
90 | zui.fatal("Memory fault: address " + addr);
91 | data[addr] = (byte)((w >> 8) & 0xff);
92 | data[addr+1] = (byte)(w & 0xff);
93 | }
94 |
95 | // Dump the specified amount of memory, starting at the specified address,
96 | // to the specified DataOutputStream.
97 | void dumpMemory(DataOutputStream dos,int addr,int len) throws IOException
98 | {
99 | dos.write(data,addr,len);
100 | }
101 |
102 | // Read in memory stored by dumpMemory.
103 | void readMemory(DataInputStream dis,int addr,int len) throws IOException
104 | {
105 | dis.read(data,addr,len);
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/main/java/com/stormpath/ozorkauth/support/ZMachinery.java:
--------------------------------------------------------------------------------
1 | package com.stormpath.ozorkauth.support;
2 |
3 | import com.zaxsoft.zmachine.Monitor;
4 | import com.zaxsoft.zmachine.ZCPU;
5 | import com.zaxsoft.zmachine.ZUserInterface;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 |
9 | import java.awt.Dimension;
10 | import java.awt.Point;
11 | import java.io.IOException;
12 | import java.io.InputStream;
13 | import java.io.OutputStream;
14 | import java.util.Vector;
15 |
16 | public class ZMachinery implements ZUserInterface {
17 |
18 | private static final Logger log = LoggerFactory.getLogger(ZMachinery.class);
19 |
20 | ZCPU cpu;
21 | Thread cpuThread;
22 | InputStream in;
23 | OutputStream out;
24 | String saveFile;
25 | Monitor monitor;
26 |
27 | public ZMachinery(String zFile, InputStream in, OutputStream out, String saveFile, Monitor monitor) {
28 | this.in = in;
29 | this.out = out;
30 | this.saveFile = saveFile;
31 | this.monitor = monitor;
32 |
33 | cpu = new ZCPU(this);
34 | cpu.initialize(zFile);
35 | cpuThread = cpu.start();
36 | }
37 |
38 | @Override
39 | public void fatal(String errmsg) {
40 |
41 | }
42 |
43 | @Override
44 | public void initialize(int ver) {
45 |
46 | }
47 |
48 | @Override
49 | public void setTerminatingCharacters(Vector chars) {
50 |
51 | }
52 |
53 | @Override
54 | public boolean hasStatusLine() {
55 | return false;
56 | }
57 |
58 | @Override
59 | public boolean hasUpperWindow() {
60 | return false;
61 | }
62 |
63 | @Override
64 | public boolean defaultFontProportional() {
65 | return false;
66 | }
67 |
68 | @Override
69 | public boolean hasColors() {
70 | return false;
71 | }
72 |
73 | @Override
74 | public boolean hasBoldface() {
75 | return false;
76 | }
77 |
78 | @Override
79 | public boolean hasItalic() {
80 | return false;
81 | }
82 |
83 | @Override
84 | public boolean hasFixedWidth() {
85 | return false;
86 | }
87 |
88 | @Override
89 | public boolean hasTimedInput() {
90 | return false;
91 | }
92 |
93 | @Override
94 | public Dimension getScreenCharacters() {
95 | return null;
96 | }
97 |
98 | @Override
99 | public Dimension getScreenUnits() {
100 | return null;
101 | }
102 |
103 | @Override
104 | public Dimension getFontSize() {
105 | return null;
106 | }
107 |
108 | @Override
109 | public Dimension getWindowSize(int window) {
110 | return null;
111 | }
112 |
113 | @Override
114 | public int getDefaultForeground() {
115 | return 0;
116 | }
117 |
118 | @Override
119 | public int getDefaultBackground() {
120 | return 0;
121 | }
122 |
123 | @Override
124 | public Point getCursorPosition() {
125 | return null;
126 | }
127 |
128 | @Override
129 | public void showStatusBar(String s, int a, int b, boolean flag) {
130 |
131 | }
132 |
133 | @Override
134 | public void splitScreen(int lines) {
135 |
136 | }
137 |
138 | @Override
139 | public void setCurrentWindow(int window) {
140 |
141 | }
142 |
143 | @Override
144 | public void setCursorPosition(int x, int y) {
145 |
146 | }
147 |
148 | @Override
149 | public void setColor(int fg, int bg) {
150 |
151 | }
152 |
153 | @Override
154 | public void setTextStyle(int style) {
155 |
156 | }
157 |
158 | @Override
159 | public void setFont(int font) {
160 |
161 | }
162 |
163 | @Override
164 | public int readLine(StringBuffer sb, int time) {
165 | int rc;
166 |
167 | while ((rc = readChar(time)) != -1 && rc != '\n') {
168 | sb.append((char)rc);
169 | }
170 |
171 | // this is a hack
172 | // When the ByteArrayInputStream is exhausted, we just want to tie things up here until the thread is killed.
173 | if (rc == -1) {
174 | synchronized (saveFile) {
175 | try {
176 | saveFile.wait();
177 | } catch (InterruptedException e) {
178 | log.error("Interrupted: {}", e.getMessage(), e);
179 | }
180 | }
181 | }
182 |
183 | return '\n';
184 | }
185 |
186 | @Override
187 | public int readChar(int time) {
188 | try {
189 | return in.read();
190 | } catch (IOException e) {
191 | e.printStackTrace();
192 | }
193 | return 0;
194 | }
195 |
196 | @Override
197 | public void showString(String s) {
198 | try {
199 | out.write(s.getBytes());
200 | synchronized (in) {
201 | in.notify();
202 | }
203 | } catch (IOException e) {
204 | e.printStackTrace();
205 | }
206 | }
207 |
208 | @Override
209 | public void scrollWindow(int lines) {
210 |
211 | }
212 |
213 | @Override
214 | public void eraseLine(int s) {
215 |
216 | }
217 |
218 | @Override
219 | public void eraseWindow(int window) {
220 |
221 | }
222 |
223 | @Override
224 | public String getFilename(String title, String suggested, boolean saveFlag) {
225 | return saveFile;
226 | }
227 |
228 | @Override
229 | public void quit() {
230 | Thread curThread = cpuThread;
231 | cpuThread = null;
232 | curThread.stop();
233 | }
234 |
235 | @Override
236 | public void restart() {
237 |
238 | }
239 |
240 | @Override
241 | public Monitor getMonitor() {
242 | return monitor;
243 | }
244 | }
245 |
--------------------------------------------------------------------------------
/src/main/java/com/stormpath/ozorkauth/service/GameService.java:
--------------------------------------------------------------------------------
1 | package com.stormpath.ozorkauth.service;
2 |
3 | import com.google.common.base.Stopwatch;
4 | import com.stormpath.sdk.account.Account;
5 | import com.stormpath.ozorkauth.model.CommandResponse;
6 | import com.stormpath.ozorkauth.support.ZMachinery;
7 | import com.zaxsoft.zmachine.Monitor;
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 | import org.springframework.beans.factory.annotation.Value;
11 | import org.springframework.stereotype.Service;
12 |
13 | import java.io.ByteArrayInputStream;
14 | import java.io.ByteArrayOutputStream;
15 | import java.io.File;
16 | import java.io.FileOutputStream;
17 | import java.io.IOException;
18 | import java.io.InputStream;
19 | import java.nio.file.FileSystems;
20 | import java.nio.file.Files;
21 | import java.nio.file.Path;
22 | import java.util.Base64;
23 |
24 | @Service
25 | public class GameService {
26 |
27 | @Value("#{ @environment['zmachine.file'] ?: '/tmp/zork1.z3' }")
28 | String zFile;
29 |
30 | @Value("#{ @environment['zmachine.save.file.path'] ?: '/tmp' }")
31 | String saveFilePath;
32 |
33 | private static final Logger log = LoggerFactory.getLogger(GameService.class);
34 |
35 | public String getSaveFile(Account account) {
36 | String id = account.getHref().substring(account.getHref().lastIndexOf("/")+1);
37 | return saveFilePath + File.separator + id + ".sav";
38 | }
39 |
40 | public void restart(Account account) {
41 | // retrieve existing game (if any) from customData
42 | account.getCustomData().remove("zMachineSaveData");
43 | account.getCustomData().save();
44 | }
45 |
46 | public void loadGameState(StringBuffer zMachineCommands, Account account) throws IOException {
47 | Stopwatch stopwatch = Stopwatch.createStarted();
48 |
49 | // retrieve existing game (if any) from customData
50 | String zMachineSaveData = (String) account.getCustomData().get("zMachineSaveData");
51 |
52 | stopwatch.stop();
53 | log.info("time to load zMachine save data from customData: " + stopwatch);
54 |
55 | if (zMachineSaveData != null) {
56 | stopwatch = Stopwatch.createStarted();
57 |
58 | // save data to file to be restored in game before sending new command
59 | byte[] rawData = Base64.getDecoder().decode(zMachineSaveData);
60 | Path p = FileSystems.getDefault().getPath("", getSaveFile(account));
61 | Files.write(p, rawData);
62 |
63 | stopwatch.stop();
64 | log.info("time to write zMachine save data to file: " + stopwatch);
65 |
66 | // setup restore command if we had custom data
67 | zMachineCommands.append("restore\n");
68 | }
69 | }
70 |
71 | public void saveGameState(Account account) throws IOException {
72 | Stopwatch stopwatch = Stopwatch.createStarted();
73 |
74 | // store save file in custom data
75 | Path p = FileSystems.getDefault().getPath("", getSaveFile(account));
76 |
77 | byte [] fileData = Files.readAllBytes(p);
78 | String saveFile = Base64.getEncoder().encodeToString(fileData);
79 | account.getCustomData().put("zMachineSaveData", saveFile);
80 | account.getCustomData().save();
81 |
82 | stopwatch.stop();
83 | log.info("time to save zMachine save data to customData: " + stopwatch);
84 | log.info("Wrote to file: " + getSaveFile(account));
85 | }
86 |
87 | public String doZMachine(StringBuffer zMachineCommands, Account account) {
88 | Stopwatch stopwatch = Stopwatch.createStarted();
89 |
90 | log.info("executing: " + zMachineCommands.toString().replace("\n", ", ") + " for: " + account.getEmail());
91 |
92 | String fileName = getSaveFile(account);
93 |
94 | // setup zmachine
95 | InputStream in = new ByteArrayInputStream(zMachineCommands.toString().getBytes());
96 | ByteArrayOutputStream out = new ByteArrayOutputStream();
97 | Monitor monitor = new Monitor();
98 | ZMachinery zMachinery = new ZMachinery(zFile, in, out, fileName, monitor);
99 |
100 | // ensure that we are done writing based on the number of input commands
101 | int numOutput = zMachineCommands.toString().split("\n").length + 1;
102 | for (int i=0; i= 0) {
116 | // ensure save file is written before killing zmachine
117 | monitor.doWait();
118 | }
119 |
120 | // kill zmachine
121 | zMachinery.quit();
122 |
123 | stopwatch.stop();
124 | log.info("time to fire up and execute zMachine commands: " + stopwatch);
125 |
126 | return res;
127 | }
128 |
129 | public CommandResponse processZMachineResponse(String zMachineRequest, String zMachineResponse) {
130 | // process output
131 | // get rid of "OK"s and prompts
132 | zMachineResponse = zMachineResponse.replace("Ok.\n\n>", "").replace(">", "");
133 |
134 | String[] tmpResponse = zMachineResponse.split("\n\n");
135 |
136 | int rLength = tmpResponse.length;
137 |
138 | String[] gameInfo = tmpResponse[0].split("\n");
139 | String[] look = (rLength < 3) ? tmpResponse[1].split("\n") : tmpResponse[2].split("\n");
140 |
141 | String[] response = (rLength >= 4) ? tmpResponse[3].split("\n") : null;
142 |
143 | // get response from zmachine
144 | CommandResponse res = new CommandResponse();
145 | res.setGameInfo(gameInfo);
146 | res.setLook(look);
147 | res.setRequest(zMachineRequest);
148 | res.setResponse(response);
149 | res.setStatus("SUCCESS");
150 |
151 | return res;
152 | }
153 |
154 | public void cleanup(Account account) throws IOException {
155 | Path p = FileSystems.getDefault().getPath("", getSaveFile(account));
156 | Files.deleteIfExists(p);
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxsoft/zmachine/ZUserInterface.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2008 Matthew E. Kimmel
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in
12 | * all copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | * THE SOFTWARE.
21 | */
22 | package com.zaxsoft.zmachine;
23 |
24 | import java.awt.*;
25 | import java.util.Vector;
26 |
27 | /**
28 | * ZUserInterface - This interface must be implemented by a programmer
29 | * using the ZMachine classes, to provide various I/O routines and
30 | * other routines that will differ with different user interfaces.
31 | *
32 | * @author Matt Kimmel
33 | */
34 | public interface ZUserInterface {
35 | // This method is called when a fatal error is encountered.
36 | // It should never return.
37 | public void fatal(String errmsg);
38 |
39 | // This method is called when the game starts or restarts,
40 | // to initialize windows and such. The version number of
41 | // the current game is passed.
42 | public void initialize(int ver);
43 |
44 | // This method sets the additional terminating characters for
45 | // READ operations. It should be called after initialize, and
46 | // should be passed a Vector of Integer objects containing
47 | // the Z-Characters that should be treated as terminating characters.
48 | public void setTerminatingCharacters(Vector chars);
49 |
50 | // This method returns true if the user interface supports a
51 | // status line.
52 | public boolean hasStatusLine();
53 |
54 | // This method returns true if the user interface supports an
55 | // upper window.
56 | public boolean hasUpperWindow();
57 |
58 | // This method returns true if the default font is variable-width.
59 | public boolean defaultFontProportional();
60 |
61 | // This method returns true if the user interface supports colors.
62 | public boolean hasColors();
63 |
64 | // This method returns true if boldface styles are available.
65 | public boolean hasBoldface();
66 |
67 | // This method returns true if italic styles are available.
68 | public boolean hasItalic();
69 |
70 | // This method returns true if fixed-width styles are available.
71 | public boolean hasFixedWidth();
72 |
73 | // This method returns true if timed input is supported.
74 | public boolean hasTimedInput();
75 |
76 | // This method returns the size of the "screen" in lines/columns.
77 | public Dimension getScreenCharacters();
78 |
79 | // This method returns the size of the "screen" in "units".
80 | public Dimension getScreenUnits();
81 |
82 | // This method returns the size of the current font in "units".
83 | public Dimension getFontSize();
84 |
85 | // This method returns the size of the specified window, in characters.
86 | public Dimension getWindowSize(int window);
87 |
88 | // These methods return the default foreground and background colors
89 | // (as Z-Machine color numbers).
90 | public int getDefaultForeground();
91 | public int getDefaultBackground();
92 |
93 | // This method returns the current cursor position.
94 | public Point getCursorPosition();
95 |
96 | // This method shows the status bar. This is handled entirely
97 | // by the user interface, rather than in the CPU or the I/O card,
98 | // in order to give the user interface programmer some flexibility
99 | // about where to put the information (in the window title bar,
100 | // for example).
101 | public void showStatusBar(String s,int a,int b,boolean flag);
102 |
103 | // Split the screen, as per SPLIT_SCREEN instruction
104 | public void splitScreen(int lines);
105 |
106 | // This method sets the current window, to which all output
107 | // will be directed. In V1-3, this implies clearing the window;
108 | // in V4+, it implies homing the cursor.
109 | public void setCurrentWindow(int window);
110 |
111 | // This method sets the cursor to position x,y (or turns the cursor on or off--
112 | // see Z-Machine spec).
113 | public void setCursorPosition(int x,int y);
114 |
115 | // This method sets the foreground and background colors. 0 means no change.
116 | public void setColor(int fg,int bg);
117 |
118 | // Set the text style (see Z-Machine spec for meaning of parameter)
119 | public void setTextStyle(int style);
120 |
121 | // Set the font--again, see Z-Spec.
122 | public void setFont(int font);
123 |
124 | // This method reads a line of user input and appends it to the
125 | // supplied StringBuffer; it returns the terminating character.
126 | // If time is nonzero, the readLine will time out if a terminating
127 | // character has not been encountered after time tenths of a second.
128 | // If a timeout occurs, the return value will be 0.
129 | public int readLine(StringBuffer sb,int time);
130 |
131 | // This method reads a character from the user and returns it. If time is
132 | // nonzero, it times out after time/10 seconds.
133 | public int readChar(int time);
134 |
135 | // Print a string
136 | public void showString(String s);
137 |
138 | // This method scrolls the current window by the specified
139 | // number of lines.
140 | public void scrollWindow(int lines);
141 |
142 | // This method erases a line in the current window.
143 | public void eraseLine(int s);
144 |
145 | // This method erases the current window.
146 | public void eraseWindow(int window);
147 |
148 | // This method gets a filename from the user, possibly using a FileDialog.
149 | // A title for a dialog box is supplied. If saveFlag is true, then we
150 | // are saving a file; otherwise, we're loading a file. The method should
151 | // return null if there was an error or the user canceled.
152 | public String getFilename(String title,String suggested,boolean saveFlag);
153 |
154 | // This function is called when the Z-Machine halts. It
155 | // should not return.
156 | public void quit();
157 |
158 | // This function is called when the Z-Machine is about to
159 | // restart. The UI should prepare by reseting itself to
160 | // an initial state. The function should return.
161 | public void restart();
162 |
163 | public Monitor getMonitor();
164 | }
165 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxsoft/README.txt:
--------------------------------------------------------------------------------
1 | ZAX v0.91
2 | Z-Code Application Executor
3 | Matt Kimmel
4 | matt at infinite-jest dot net
5 | 9/14/2008
6 |
7 | LEGAL STUFF
8 |
9 | As of version 0.91, Zax is now open source, licensed under the MIT License. Please see the accompanying LICENSE.txt file for legal details.
10 |
11 | DISCLAIMER
12 |
13 | Zax was my first major Java project, and most of it was written in 1996 and 1997. Hence, there are a lot of idioms in the code that are more C-like and less Java-like. Please bear with me; I'm fixing them as I work on the code.
14 |
15 | WHAT IS ZAX?
16 |
17 | Zax is a Java 2 implementation of the Z-Machine. An explanation of the Z-Machine is beyond the scope of this document, but in a nutshell, it is a virtual machine that runs programs written in Z-code, which includes all of Infocom's text adventures and a large number of text adventures and other programs written using the Inform compiler. Information about all of this is available at the Interactive Fiction Archive, at ftp://ftp.gmd.de/if-archive.
18 |
19 | Zax is capable of running Z-code programs of versions 1 through 8, with the exception of version 6 programs. It complies with the Z-Machine standard v0.2 as specified by Graham Nelson (with a couple of exceptions; see below). It runs all of the non-graphical Infocom games except Beyond Zork (which exhibits some trouble with its on-screen map), and quite a lot of the more modern games written with Inform.
20 |
21 | Zax is written entirely in Java, and should run on any Java Virtual Machine of version 1.5 or higher.
22 |
23 | Please note that Zax is a Java application, not an applet, and is therefore not suitable for running in a browser. Zax was developed independently of ZPlet, which is a Java applet implementation of the Z-Machine released around the same time as Zax's first release (circa 1997).
24 |
25 |
26 | USING ZAX
27 |
28 | If you have retrieved the official Zax distribution, you should have a "bin" directory containing a complete binary JAR of Zax, called zax.jar. To run Zax, you can use the command-line Java launcher included with the Java Runtime Environment, like this:
29 |
30 | java -jar zax.jar
31 |
32 | On some systems, you may also be able to double-click on the JAR file in a desktop file explorer to run Zax.
33 |
34 | Once Zax is running, just select Play Story... under the File menu and you're on your way.
35 |
36 |
37 | BUILDING ZAX
38 |
39 | Zax comes with a very rudimentary ant build file. You can build all the classes using a command like this:
40 |
41 | ant -f zax.xml all
42 |
43 | You can also create an Eclipse or IntelliJ IDEA or Netbeans (or IDE of your choice) project around the source files and build that way. Zax currently has no dependencies outside of the standard Java libraries, which makes things easy.
44 |
45 |
46 | HOW ZAX WORKS
47 |
48 | Zax is composed of three components: A GUI front-end (Zax); a generic Z-Machine implementation (package ZMachine); and a text-screen widget used by the front-end. The ZMachine package is implemented in such a way that the user interface is completely abstracted. When the CPU is initialized, it is passed an instance of an object that implements the interface ZUserInterface; this interface is a set of generic methods for various fundamental user-interface functions (such as text output and Z-Machine window management) which can be implemented in any way that the interface programmer finds desirable. My front-end uses a custom AWT widget designed to emulate an IBM Text-Mode-style screen, but pretty much any user interface is possible.
49 |
50 | The Zax user interface consists of two classes: Zax, which implements both a high-level interface (providing functions such as "Play Story"), and ZaxWindow, which is used by Zax to keep track of logical Z-Machine windows. Zax is a subclass of java.awt.Frame. Zax uses the kimmel.awt.TextScreen class to display and manipulate text.
51 |
52 | The ZMachine package consists of several classes. ZMachine.ZCPU is the heart of the Z-Machine; it implements the CPU and all of its opcodes. ZMemory is the Z-Machine's memory manager; it is responsible for initializing memory, encapsulating access to it, and dumping it to and reading it from a stream. ZIOCard encapsulates the Z-Machine I/O streams (in most cases passing read and write requests to and from the user interface). ZObjectTable encapsulates the Z-Machine's object table stuctures (including properties and attributes); it relies heavily on ZMemory to access the internal Z-Machine data associated with these structures. ZCallFrame is a class which represents a frame on the Z-Machine call stack. Finally, as mentioned above, ZUserInterface is an interface which must be implemented by user interface code.
53 |
54 | Full source code is included in the Zax distribution. It is designed in such a way that enhancements could be made by subclassing existing code, rather than modifying it.
55 |
56 |
57 | BUGS
58 |
59 | I know of a few bugs and missing features in Zax. These include:
60 |
61 | o Does not implement transcript or command script I/O streams (this falls short of Nelson's v0.2 specification).
62 | o Problems with window handling in the user interface make "Beyond Zork" unplayable. Every other game I've tried has been fine, but then, I haven't tried everything that's out there.
63 | o Sometimes when the Zax window goes out of focus and then comes back into focus, it will not accept keystrokes. This seems to be a bug in the Java AWT; usually it can be fixed by clicking on the text window a few times.
64 | o Calling the user interface "minimal" wuld be an understatement. But it works.
65 |
66 | If you find any bugs, I'd like to hear about them. Please e-mail me with details; in particular, let me know which game the bug occurs in, what you do to cause the bug to happen, and, if possible, the version number of the game.
67 |
68 |
69 | FUTURE PLANS:
70 | To be clear: The 0.91 release is intended to put a version of Zax out there that compiles/runs on modern versions of Java, and to make it open source. There are no other functional differences between 0.9 and 0.91. The code is essentially the same, and could use plenty of refactoring, cleanup, and maybe even some unit tests. Don't judge me too harshly--Zax was the first major program I wrote in Java and I've learned an awful lot in the 11 years since it was released.
71 |
72 | Things have changed a lot in the IF and Z-code world since Zax was originally released in 1997. Zax needs a lot of work to make it a "modern" Z-code interpreter. It should be made Z-machine standard 1.0 compliant. It needs Quetzal support. It could use Blorb support. It probably needs some modification to accommodate story files generated by Inform 7.
73 |
74 |
75 | THE FINAL WORD
76 |
77 | Zax was originally written with the noble intent of creating a truly cross-platform Z-machine. I had a lot of plans for it, but life circumstances severely diminished the amount of free time I had to spend on the project, so it never developed past its original release.
78 |
79 | So why am I re-releasing it 11 years later? Mainly because I still see it mentioned on RAIF from time to time, and because the original license was pretty restrictive (written before I was an open-source convert!), and because there are faint indications out there that it might still be, in some way, useful.
80 |
81 | I'd love to hear any comments, questions, bug reports, rants, harangues, compliments, complaints, limericks, etc. Please feel free to send me e-mail; my e-mail address is matt at infinite-jest dot net. There's no web "home" for Zax at the time, but for now, any new versions will certainly be uploaded to the IF-Archive.
82 |
83 | -Matt Kimmel
84 | August 25, 1997, and
85 | September 14, 2008.
86 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxsoft/zmachine/ZIOCard.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2008 Matthew E. Kimmel
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in
12 | * all copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | * THE SOFTWARE.
21 | */
22 | package com.zaxsoft.zmachine;
23 |
24 | import java.awt.*;
25 | import java.util.StringTokenizer;
26 |
27 | /**
28 | * This class provides various I/O functions to the ZCPU.
29 | * It uses an object that implements the ZUserInterface interface
30 | * (passed to it in the initialize method) to provide these functions.
31 | *
32 | * @author Matt Kimmel
33 | */
34 | class ZIOCard extends Object {
35 | private ZUserInterface zui;
36 | private ZMemory memory;
37 | private int version;
38 | private int inputStream, outputStream;
39 | private boolean buffer; // If true, we only output text to the screen when the outputFlush method is called.
40 | private StringBuffer outputBuffer;
41 | private boolean[] isOpen = {false,true,true,true,true};
42 | private int baseMemAddr; // Base address of output stream 3 (or start of line in multiline mode)
43 | private int curMemAddr; // Current address on output stream 3
44 | private int memWidth; // Width of output stream 3
45 | private int memCursorX; // X position of cursor on output stream 3
46 | private boolean memMultiLine; // Is output stream 3 multiline?
47 |
48 | // The initialize method performs necessary initializations.
49 | // It is passed the ZUserInterface and ZMemory objects for
50 | // this ZMachine, as well as the version number of the
51 | // storyfile.
52 | void initialize(ZUserInterface ui,ZMemory mem,int ver,boolean buf)
53 | {
54 | zui = ui;
55 | memory = mem;
56 | version = ver;
57 | outputStream = 1;
58 | inputStream = 0;
59 | buffer = buf;
60 | outputBuffer = new StringBuffer();
61 | }
62 |
63 | // Print a string to the current output stream
64 | void printString(String s)
65 | {
66 | int n;
67 |
68 | // Ignore anything destined for a closed stream
69 | if (isOpen[outputStream] == false)
70 | return;
71 | switch (outputStream) {
72 | case 1 : // Screen -- currently, we send this right on to the user interface unless buffering
73 | if (buffer)
74 | outputBuffer.append(s);
75 | else
76 | zui.showString(s);
77 | break;
78 | case 2 : // For now, transcript stuff goes to stdout
79 | System.out.print(s);
80 | break;
81 | case 3 : // Memory
82 | if (!memMultiLine) { // Single-line mode--this is easy
83 | for (n = 0;n < s.length();n++)
84 | memory.putByte(curMemAddr+n,s.charAt(n));
85 | curMemAddr += s.length();
86 | memory.putWord(baseMemAddr,(memory.fetchWord(baseMemAddr) + s.length()));
87 | }
88 | else { // Multi-line mode. Bleh.
89 | // First, make a StringTokenizer out of this string
90 | StringTokenizer st = new StringTokenizer(s,"\n ",true);
91 | while (st.hasMoreTokens()) {
92 | String tok = st.nextToken();
93 | if (tok.equals("\n")) { // Start a new line
94 | memory.putWord(curMemAddr,0);
95 | baseMemAddr = curMemAddr;
96 | curMemAddr += 2;
97 | }
98 | else { // Add to this line, wrap if necessary
99 | if ((memCursorX + tok.length()) > (memWidth - 2)) { // Wrap
100 | memory.putWord(curMemAddr,0);
101 | baseMemAddr = curMemAddr;
102 | curMemAddr += 2;
103 | }
104 | for (n = 0;n < tok.length();n++)
105 | memory.putByte(curMemAddr+n,tok.charAt(n));
106 | curMemAddr += tok.length();
107 | memory.putWord(baseMemAddr,(memory.fetchWord(baseMemAddr) + tok.length()));
108 | }
109 | }
110 | }
111 | break;
112 | case 4 : // Command script--not implemented
113 | break;
114 | }
115 | }
116 |
117 | // Flush the output buffer by sending its contents to the user interface.
118 | // If we're not buffering, ignore.
119 | void outputFlush()
120 | {
121 | if (!buffer)
122 | return;
123 | if (outputBuffer.length() == 0)
124 | return;
125 |
126 | zui.showString(outputBuffer.toString());
127 | outputBuffer = new StringBuffer();
128 | }
129 |
130 | // Set output stream.
131 | void setOutputStream(int s,int baddr,int w,boolean multiLine)
132 | {
133 | if (s < 0) {
134 | isOpen[-s] = false;
135 | outputStream = 1; // This doesn't seem to be in the specification--or am I missing it?
136 | return;
137 | }
138 |
139 | if ((s == 0) || (s > 4))
140 | zui.fatal("Illegal output stream: " + s);
141 |
142 | if (s == 3) { // Open a memory stream
143 | memMultiLine = multiLine;
144 | baseMemAddr = baddr;
145 | memory.putWord(baseMemAddr,0);
146 | curMemAddr = baseMemAddr + 2;
147 | if (memMultiLine) {
148 | if (w < 0)
149 | memWidth = -w;
150 | else {
151 | Dimension d = zui.getWindowSize(w);
152 | memWidth = d.width;
153 | }
154 | memCursorX = 0;
155 | }
156 | outputStream = 3;
157 | isOpen[3] = true;
158 | }
159 | else if (s == 4)
160 | zui.fatal("Output stream 4 not yet supported");
161 | else {
162 | outputStream = s;
163 | isOpen[s] = true;
164 | }
165 | }
166 |
167 | // Set input stream
168 | void setInputStream(int s)
169 | {
170 | if ((s < 0) || (s > 1))
171 | zui.fatal("Illegal input stream: " + s);
172 |
173 | if (s == 1)
174 | zui.fatal("Input stream 1 unsupported");
175 | else
176 | inputStream = s;
177 | }
178 |
179 | // Read from current input stream. Currently just uses zui.readString().
180 | // If time is nonzero, time out after time tenths of a second.
181 | // Return 0 if there was a timeout.
182 | int readLine(StringBuffer sb,int time)
183 | {
184 | return zui.readLine(sb,time);
185 | }
186 |
187 | // Read a character from the current input stream.
188 | int readChar(int time)
189 | {
190 | int c;
191 |
192 | c = zui.readChar(time);
193 | return c;
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/src/main/java/com/stormpath/ozorkauth/controller/GameController.java:
--------------------------------------------------------------------------------
1 | package com.stormpath.ozorkauth.controller;
2 |
3 | import com.google.common.base.Strings;
4 | import com.stormpath.sdk.account.Account;
5 | import com.stormpath.sdk.application.Application;
6 | import com.stormpath.sdk.client.Client;
7 | import com.stormpath.sdk.servlet.account.AccountResolver;
8 | import com.stormpath.ozorkauth.model.CommandRequest;
9 | import com.stormpath.ozorkauth.model.CommandResponse;
10 | import com.stormpath.ozorkauth.model.Registration;
11 | import com.stormpath.ozorkauth.service.GameService;
12 | import org.springframework.beans.factory.annotation.Autowired;
13 | import org.springframework.beans.factory.annotation.Value;
14 | import org.springframework.http.HttpStatus;
15 | import org.springframework.web.bind.annotation.ExceptionHandler;
16 | import org.springframework.web.bind.annotation.RequestBody;
17 | import org.springframework.web.bind.annotation.RequestMapping;
18 | import org.springframework.web.bind.annotation.RequestMethod;
19 | import org.springframework.web.bind.annotation.ResponseStatus;
20 | import org.springframework.web.bind.annotation.RestController;
21 |
22 | import javax.servlet.http.HttpServletRequest;
23 | import javax.servlet.http.HttpServletResponse;
24 | import java.io.IOException;
25 |
26 | @RestController
27 | public class GameController {
28 |
29 | @Value("#{ @environment['zmachine.file'] ?: '/app/zork1.z3' }")
30 | String zFile;
31 |
32 | @Value("#{ @environment['zmachine.save.file.path'] ?: '/tmp' }")
33 | String saveFilePath;
34 |
35 | @Autowired
36 | GameService gameService;
37 |
38 | @Autowired
39 | Client client;
40 |
41 | @Autowired
42 | Application application;
43 |
44 | @RequestMapping("/")
45 | public void root(HttpServletResponse res) throws IOException {
46 | res.sendRedirect("/v1/instructions");
47 | }
48 |
49 | @RequestMapping("/v1/instructions")
50 | public CommandResponse home(HttpServletRequest req) {
51 | String proto = (req.getHeader("x-forwarded-proto") != null) ?
52 | req.getHeader("x-forwarded-proto") : req.getScheme() ;
53 | String server = req.getServerName();
54 | String port = (req.getServerPort() == 80 || req.getServerPort() == 443) ? "" : ":" + req.getServerPort();
55 | String baseUrl = proto + "://" + server + port;
56 |
57 | CommandResponse res = new CommandResponse();
58 |
59 | String[] response = {
60 | "Welcome to the interactive OAuth2 Text Based Adventure!",
61 | "",
62 | "In order to play the game, you must:",
63 | " 1. Register an account",
64 | " 2. Get an access token using your account",
65 | " 3. Use the access token to send commands to the game",
66 | "",
67 | "To Register, you send a POST request to the registration endpoint (the below example uses httpie):",
68 | " http POST " + baseUrl + "/v1/r givenName= surName= email= password=",
69 | "",
70 | "To get an access token, you send a POST request to the oauth endpoint (the below example uses httpie):",
71 | " http -f POST " + baseUrl + "/v1/a Origin:" + baseUrl + " grant_type=password username= password=",
72 | "Note: The above command returns an access token and a refresh token. When the access token expires, you can use the refresh token to get a new access token.",
73 | "",
74 | "To use the access token to interact with the game, you send a POST request to the command endpoint (the below example uses httpie):",
75 | " http POST " + baseUrl + "/v1/c Authorization:'Bearer '",
76 | " http POST " + baseUrl + "/v1/c Authorization:'Bearer ' request='go north'",
77 | "Note: if you don't send the request parameter, the response will contain the result of looking around your current location in the game",
78 | "",
79 | "Part of the game is discovering which language elements work to move you forward in the game.",
80 | "If you are impatient, here's a list of all the available commands: http://zork.wikia.com/wiki/Command_List"
81 | };
82 |
83 | res.setResponse(response);
84 | res.setStatus("SUCCESS");
85 |
86 | return res;
87 | }
88 |
89 | @RequestMapping(value = "/v1/r", method = RequestMethod.POST)
90 | public CommandResponse register(@RequestBody Registration registration) {
91 |
92 | if (
93 | registration == null ||
94 | Strings.isNullOrEmpty(registration.getGivenName()) || Strings.isNullOrEmpty(registration.getSurName()) ||
95 | Strings.isNullOrEmpty(registration.getEmail()) || Strings.isNullOrEmpty(registration.getPassword())
96 | ) {
97 | throw new RegistrationException("givenName, surName, email and password are all required to register.");
98 | }
99 |
100 | Account account = client.instantiate(Account.class);
101 | account
102 | .setEmail(registration.getEmail())
103 | .setPassword(registration.getPassword())
104 | .setGivenName(registration.getGivenName())
105 | .setSurname(registration.getSurName());
106 |
107 | account = application.createAccount(account);
108 |
109 | CommandResponse res = new CommandResponse();
110 | res.setStatus("SUCCESS");
111 | String[] response = {
112 | "Thank you for registering!",
113 | account.getGivenName() + " " + account.getSurname(),
114 | account.getEmail()
115 | };
116 | res.setResponse(response);
117 |
118 | return res;
119 | }
120 |
121 | @RequestMapping(value = "/v1/c", method = RequestMethod.POST)
122 | public CommandResponse command(@RequestBody(required = false) CommandRequest commandRequest, HttpServletRequest req) throws IOException {
123 | Account account = AccountResolver.INSTANCE.getAccount(req);
124 |
125 | String zMachineRequest = (commandRequest != null) ? commandRequest.getRequest() : null;
126 | StringBuffer zMachineCommands = new StringBuffer();
127 |
128 | //check for restart
129 | if ("restart".equals(zMachineRequest)) {
130 | gameService.restart(account);
131 | zMachineRequest = null;
132 | } else {
133 | // restore games state from customData, if it exists
134 | gameService.loadGameState(zMachineCommands, account);
135 | }
136 |
137 | // we always want to look
138 | zMachineCommands.append("look\n");
139 |
140 | // setup passed in command
141 | if (zMachineRequest != null) {
142 | zMachineCommands.append(zMachineRequest + "\n");
143 | zMachineCommands.append("save\n");
144 | }
145 |
146 | // execute game move (which may just be looking)
147 | String zMachineResponse = gameService.doZMachine(zMachineCommands, account);
148 |
149 | CommandResponse res = gameService.processZMachineResponse(zMachineRequest, zMachineResponse);
150 |
151 | if (zMachineRequest != null) {
152 | gameService.saveGameState(account);
153 | }
154 |
155 | gameService.cleanup(account);
156 |
157 | // return response
158 | return res;
159 | }
160 |
161 | @ExceptionHandler(IOException.class)
162 | @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
163 | public CommandResponse internalError(IOException ex) {
164 | return handleException(ex);
165 | }
166 |
167 | @ExceptionHandler(RegistrationException.class)
168 | @ResponseStatus(HttpStatus.BAD_REQUEST)
169 | public CommandResponse badRegistration(RegistrationException ex) {
170 | return handleException(ex);
171 | }
172 |
173 | private CommandResponse handleException(Exception ex) {
174 | CommandResponse res = new CommandResponse();
175 | res.setStatus("ERROR");
176 | res.setMessage(ex.getMessage());
177 | return res;
178 | }
179 |
180 | class RegistrationException extends RuntimeException {
181 | public RegistrationException(String message) {
182 | super(message);
183 | }
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Welcome to OZorkAuth
2 |
3 | The purpose of this project is to learn a little about OAuth2 flows and have fun doing it.
4 |
5 | Following the instructions below, you'll be able to play the classic text-based adventure,
6 | [Zork I](https://en.wikipedia.org/wiki/Zork) by interacting with an API via OAuth2.
7 |
8 | This is a Spring Boot app powered by the [Stormpath](http://stormpath.com) intgeration which provides out-of-the-box
9 | authentication and authorization through its SaaS platform, including OAuth2. It also uses the
10 | [Zax](https://github.com/mattkimmel/zax) Java based [Z-Machine](https://en.wikipedia.org/wiki/Z-machine) by Matt Kimmel.
11 |
12 | *Full Disclosure*: I work for Stormpath.
13 |
14 | A live hosted version of this project can be found [here](https://ozorkauth.herokuapp.com/v1/instructions).
15 |
16 | Or, you can deploy this app to your own Heroku account by clicking the purple button:
17 |
18 | [](https://heroku.com/deploy)
19 |
20 |
21 | ## A Word on OAuth2
22 |
23 | The [OAuth2](https://tools.ietf.org/html/rfc6749) specification is very broad. This example focuses
24 | on just two of the workflows: `grant_type=password` and `grant_type=refresh_token`
25 |
26 | The `grant_type=password` flow is an [*authorization grant*](https://tools.ietf.org/html/rfc6749#section-1.3.3) used
27 | to return an access token (and possibly a refresh token) when a valid username and password are presented. This
28 | alleviates the need to pass username and password on every request. Once you have an access token, you can present
29 | this token to access protected resources.
30 |
31 | The `grant_type=refresh_token` flow is used to exchange a refresh token for a new access token. Typically, an access
32 | token will be short lived and a refresh token will be longer lived. When the access token expires, the next request
33 | attempt using the access token will result in Bad Request (400) response. As long as the refresh token has not expired,
34 | it can be used to obtain a new access token. Once the refresh token is expired, the user must login once again and get
35 | a new access token and refresh token pair.
36 |
37 | ## How to play Zork, OAuth Style
38 |
39 | There are 4 steps to be able to play the game:
40 |
41 | 1. Register for an account (this has nothing to do with OAuth2)
42 | 2. Get an access token and a refresh token (this is the `grant_type=password` flow)
43 | 3. Use the access token to interact with the game
44 | 4. When the access token expires, use the refresh token to get a new one (this is the `grant_type=refresh_token`)
45 |
46 | *Note*: All the examples below use [httpie](https://github.com/jkbrzt/httpie). On Mac, this can be installed with:
47 |
48 | ```
49 | brew install httpie
50 | ```
51 |
52 | ### Register for an Account
53 |
54 | The OZorkAuth API exposes an endpoint for registration: `/v1/r`. Behind the scenes, an account will be created using the
55 | Stormpath API.
56 |
57 | Here's an example of the registration command for the sample app running on Heroku:
58 |
59 | ```
60 | http POST \
61 | https://ozorkauth.herokuapp.com/v1/r \
62 | givenName=Bob surName=Smith email=bob@smith.com password=Password
63 | ```
64 |
65 | *Note*: Of course the above data, including the password is a made-up example. You should create your account using your own real information, including a strong password.
66 |
67 | You get a response that looks like this:
68 |
69 | ```
70 | {
71 | "response": [
72 | "Thank you for registering!",
73 | "Bob Smith",
74 | "bob@smith.com"
75 | ],
76 | "status": "SUCCESS"
77 | }
78 | ```
79 |
80 | ### Get an Access Token and a Refresh Token
81 |
82 | The OZorkAuth API exposes an endpoint for authorization: `/v1/a`. Behind the scenes, Stormpath verifies that the
83 | username and password are valid and, if so, returns an access token and a refresh token.
84 |
85 | Here's an example of the authorization command for the sample app running on Heroku:
86 |
87 | ```
88 | http -f POST \
89 | https://ozorkauth.herokuapp.com/v1/a \
90 | Origin:https://ozorkauth.herokuapp.com \
91 | grant_type=password username=bob@smith.com password=Password
92 | ```
93 |
94 | *Note*: The `-f` (form submission) parameter to `httpie` above is critical as it ensures that the `Content-Type` header
95 | is set to: `application/x-www-form-urlencoded`, a requirement of the `grant_type=password` flow per the
96 | [specification](https://tools.ietf.org/html/rfc6749#section-4.3.2).
97 |
98 | You get a response that looks like this:
99 |
100 | ```
101 | {
102 | "access_token": "eyJraWQiOiJSOTJTQkhKQzFVNERBSU1HUTNNSE9HVk1YIiwiYWxnIjoiSFMyNTYifQ...",
103 | "expires_in": 3600,
104 | "refresh_token": "eyJraWQiOiJSOTJTQkhKQzFVNERBSU1HUTNNSE9HVk1YIiwiYWxnIjoiSFMyNTYifQ...",
105 | "token_type": "Bearer"
106 | }
107 | ```
108 |
109 | *Note*: The `access_token` and `refresh_token` values above are truncated for brevity.
110 |
111 | Save the values of the `access_token` and `refresh_token` for use in the following steps.
112 |
113 | ### The Fun Stuff: Use the Access Token to Interact with the Game
114 |
115 | The OZorkAuth API exposes an endpoint for sending commands to the game: `/v1/c`. Behind the scenes, this
116 | endpoint is secured using the Stormpath Spring Security Spring Boot WebMVC integrations. If the request
117 | is not properly authenticated, it will be rejected.
118 |
119 | You use the access token in the `Authorization` header, which the Stormpath Spring Security integration
120 | looks up to verify it as valid.
121 |
122 | Here's an example of the game command for the sample app running on Heroku:
123 |
124 | ```
125 | http POST \
126 | https://ozorkauth.herokuapp.com/v1/c \
127 | Authorization:"Bearer eyJraWQiOiJSOTJTQkhKQzFVNERBSU1HUTNNSE9HVk1YIiwiYWxnIjoiSFMyNTYifQ..."
128 | ```
129 |
130 | Without any other parameters, the response will be the result of looking at your surroundings in the game:
131 |
132 | ```
133 | {
134 | "gameInfo": [
135 | "ZORK I: The Great Underground Empire",
136 | "Copyright (c) 1981, 1982, 1983 Infocom, Inc. All rights reserved.",
137 | "ZORK is a registered trademark of Infocom, Inc.",
138 | "Revision 88 / Serial number 840726"
139 | ],
140 | "look": [
141 | "West of House",
142 | "You are standing in an open field west of a white house, with a boarded front door.",
143 | "There is a small mailbox here."
144 | ],
145 | "status": "SUCCESS"
146 | }
147 | ```
148 |
149 | To issue a command in the game, use the above and include the `request=` parameter:
150 |
151 | ```
152 | http POST \
153 | https://ozorkauth.herokuapp.com/v1/c \
154 | Authorization:"Bearer eyJraWQiOiJSOTJTQkhKQzFVNERBSU1HUTNNSE9HVk1YIiwiYWxnIjoiSFMyNTYifQ..." \
155 | request="open mailbox"
156 | ```
157 |
158 | The response will look like this:
159 |
160 | ```
161 | {
162 | "gameInfo": [
163 | "ZORK I: The Great Underground Empire",
164 | "Copyright (c) 1981, 1982, 1983 Infocom, Inc. All rights reserved.",
165 | "ZORK is a registered trademark of Infocom, Inc.",
166 | "Revision 88 / Serial number 840726"
167 | ],
168 | "look": [
169 | "West of House",
170 | "You are standing in an open field west of a white house, with a boarded front door.",
171 | "There is a small mailbox here."
172 | ],
173 | "request": "open mailbox",
174 | "response": [
175 | "Opening the small mailbox reveals a leaflet."
176 | ],
177 | "status": "SUCCESS"
178 | }
179 | ```
180 |
181 | *Note*: On each successful request, the game state is automatically saved as [Custom Data](https://docs.stormpath.com/rest/product-guide/latest/accnt_mgmt.html#how-to-store-additional-user-information-as-custom-data) to the Stormpath Account the `access_token` is associated with.
182 |
183 | You can keep executing commands in this way until the `access_token` expires.
184 |
185 | ### Use the Refresh Token to Get a New Access Token
186 |
187 | Once again use the authorization endpoint: `/v1/a`
188 |
189 | Only this time, we use the `grant_type=refresh_token` OAuth2 flow.
190 |
191 | Here's an example of the authorization command for the sample app running on Heroku:
192 |
193 | ```
194 | http -f POST \
195 | https://ozorkauth.herokuapp.com/v1/a \
196 | Origin:https://ozorkauth.herokuapp.com \
197 | grant_type=refresh_token refresh_token=eyJraWQiOiJSOTJTQkhKQzFVNERBSU1HUTNNSE9HVk1YIiwiYWxnIjoiSFMyNTYifQ...
198 | ```
199 |
200 | You will get a similar response as before when using the `grant_type=password` flow:
201 |
202 | ```
203 | {
204 | "access_token": "eyJraWQiOiJSOTJTQkhKQzFVNERBSU1HUTNNSE9HVk1YIiwiYWxnIjoiSFMyNTYifQ...",
205 | "expires_in": 3600,
206 | "refresh_token": "eyJraWQiOiJSOTJTQkhKQzFVNERBSU1HUTNNSE9HVk1YIiwiYWxnIjoiSFMyNTYifQ...",
207 | "token_type": "Bearer"
208 | }
209 | ```
210 |
211 | *Note*: The `refresh_token` returned in the response will be the same while the `access_token` will be new.
212 |
213 | ## Acknowledgements & More Info
214 |
215 | The Zork I game play is accomplished using the [Zax](https://github.com/mattkimmel/zax) Java based [Z-Machine](https://en.wikipedia.org/wiki/Z-machine) by Matt Kimmel.
216 |
217 | The only change to the original source I made was to add a monitor to make the controller thread wait until the Zax thread has finished writing the game save file.
218 | This is needed as Zax is intended to be used synchronously and the HTTP based API is inherently asynchronous.
219 |
220 | For more information on Stormpath and the supported languages and integrations, checkout out the [docs](https://docs.stormpath.com).
221 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxsoft/zmachine/ZObjectTable.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2008 Matthew E. Kimmel
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in
12 | * all copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | * THE SOFTWARE.
21 | */
22 | package com.zaxsoft.zmachine;
23 |
24 | /**
25 | * ZObjectTable - Encapsulation of the Z-Machine object table.
26 | *
27 | * @author Matt Kimmel
28 | */
29 | class ZObjectTable extends Object {
30 | // Local variables
31 | private ZUserInterface zui; // User interface object
32 | private ZMemory memory; // This Z-Machine's memory object
33 | private int version; // Version number of current storyfile
34 | private int objTable; // Address of object table
35 | private int defaultsSize; // Size of property default table, in bytes
36 | private int objAttrSize; // Size of an object's attribute table, in bytes
37 | private int objHandleSize; // Size of an object handle, in bytes
38 | private int objEntrySize; // Size of an object entry in the table
39 |
40 | // The initialize routine passes a handle to the Z-Machine's
41 | // memory, as well as to the user interface and to the version
42 | // of the current storyfile.
43 | void initialize(ZUserInterface ui,ZMemory mem,int ver)
44 | {
45 | zui = ui;
46 | memory = mem;
47 | version = ver;
48 |
49 | objTable = memory.fetchWord(0x0a);
50 | if (version <= 3) {
51 | defaultsSize = 62;
52 | objAttrSize = 4;
53 | objHandleSize = 1;
54 | }
55 | else {
56 | defaultsSize = 126;
57 | objAttrSize = 6;
58 | objHandleSize = 2;
59 | }
60 | objEntrySize = (objAttrSize + (3 * objHandleSize) + 2);
61 | }
62 |
63 | /////////////////////////////////////////////////////////////////
64 | // Object manipulation routines
65 | /////////////////////////////////////////////////////////////////
66 |
67 | // Return the sibling of an object
68 | int getSibling(int obj)
69 | {
70 | int sib;
71 |
72 | if (version <= 3)
73 | sib = memory.fetchByte(objTable + defaultsSize + ((obj - 1) * objEntrySize) + objAttrSize + objHandleSize);
74 | else
75 | sib = memory.fetchWord(objTable + defaultsSize + ((obj - 1) * objEntrySize) + objAttrSize + objHandleSize);
76 |
77 | return sib;
78 | }
79 |
80 | // Set the sibling of an object
81 | void setSibling(int obj,int sib)
82 | {
83 | if (version <= 3)
84 | memory.putByte((objTable + defaultsSize + ((obj - 1) * objEntrySize) + objAttrSize + objHandleSize),sib);
85 | else
86 | memory.putWord((objTable + defaultsSize + ((obj - 1) * objEntrySize) + objAttrSize + objHandleSize),sib);
87 | }
88 |
89 | // Return the first child of an object
90 | int getChild(int obj)
91 | {
92 | int child;
93 |
94 | if (version <= 3)
95 | child = memory.fetchByte(objTable + defaultsSize + ((obj - 1) * objEntrySize) + objAttrSize + (2 * objHandleSize));
96 | else
97 | child = memory.fetchWord(objTable + defaultsSize + ((obj - 1) * objEntrySize) + objAttrSize + (2 * objHandleSize));
98 |
99 | return child;
100 | }
101 |
102 | // Set the child of an object
103 | void setChild(int obj,int child)
104 | {
105 | if (version <= 3)
106 | memory.putByte((objTable + defaultsSize + ((obj - 1) * objEntrySize) + objAttrSize + (2 * objHandleSize)),child);
107 | else
108 | memory.putWord((objTable + defaultsSize + ((obj - 1) * objEntrySize) + objAttrSize + (2 * objHandleSize)),child);
109 | }
110 |
111 | // Return an object's parent
112 | int getParent(int obj)
113 | {
114 | int parent;
115 |
116 | if (version <= 3)
117 | parent = memory.fetchByte(objTable + defaultsSize + ((obj - 1) * objEntrySize) + objAttrSize);
118 | else
119 | parent = memory.fetchWord(objTable + defaultsSize + ((obj - 1) * objEntrySize) + objAttrSize);
120 |
121 | return parent;
122 | }
123 |
124 | // Set the parent of an object
125 | void setParent(int obj,int parent)
126 | {
127 | if (version <= 3)
128 | memory.putByte((objTable + defaultsSize + ((obj - 1) * objEntrySize) + objAttrSize),parent);
129 | else
130 | memory.putWord((objTable + defaultsSize + ((obj - 1) * objEntrySize) + objAttrSize),parent);
131 | }
132 |
133 | // Given its (non-zero) parent, remove an object from the
134 | // sibling chain.
135 | void removeObject(int parent,int obj)
136 | {
137 | int curObj, prevObj;
138 |
139 | // It is legal for parent to be 0, in which case we just return.
140 | if (parent == 0)
141 | return;
142 |
143 | curObj = getChild(parent);
144 | if (curObj == 0)
145 | zui.fatal("Corrupted object table");
146 | if (curObj == obj) {
147 | // Remove the object
148 | setChild(parent,getSibling(obj));
149 | setSibling(obj,0);
150 | setParent(obj,0);
151 | return;
152 | }
153 |
154 | // Traverse the sibling chain until we find the object
155 | // and its predecessor.
156 | prevObj = curObj;
157 | curObj = getSibling(prevObj);
158 | while ((curObj != obj) && (curObj != 0)) {
159 | prevObj = curObj;
160 | curObj = getSibling(prevObj);
161 | }
162 |
163 | // If we get here, curObj is either the object we're looking
164 | // for or 0 (which is an error).
165 | if (curObj == 0)
166 | zui.fatal("Corrupted object table");
167 |
168 | // Remove the object from the chain, and set its sibling and parent to 0.
169 | setSibling(prevObj,getSibling(curObj));
170 | setSibling(obj,0); // Is this necessary?
171 | setParent(obj,0);
172 | }
173 |
174 |
175 | // Insert obj1 as obj2's first child.
176 | void insertObject(int obj1,int obj2)
177 | {
178 | int oldparent;
179 | int oldfirst;
180 |
181 | // First, remove the given object from its current
182 | // position (if any).
183 | oldparent = getParent(obj1);
184 | if (oldparent > 0)
185 | removeObject(oldparent,obj1);
186 |
187 | // Now insert it.
188 | oldfirst = getChild(obj2);
189 | setSibling(obj1,oldfirst);
190 | setChild(obj2,obj1);
191 | setParent(obj1,obj2);
192 | }
193 |
194 | //////////////////////////////////////////////////////////////
195 | // Property manipulation routines
196 | //////////////////////////////////////////////////////////////
197 |
198 | // Return the length of the property starting at the given
199 | // byte address
200 | int getPropertyLength(int baddr)
201 | {
202 | int b;
203 | int length;
204 |
205 | b = memory.fetchByte(baddr-1);
206 | if (version < 4)
207 | length = (((b >> 5) & 0x07) + 1);
208 | else {
209 | if ((b & 0x80) == 0x80)
210 | length = (b & 0x3f);
211 | else
212 | length = (((b >> 6) & 0x01) + 1);
213 | }
214 | return length;
215 | }
216 |
217 | // Get the address of the property list for the specified object.
218 | int getPropertyList(int obj)
219 | {
220 | int addr;
221 |
222 | addr = memory.fetchWord(objTable + defaultsSize + ((obj - 1) * objEntrySize) + objAttrSize + (3 * objHandleSize));
223 | return addr;
224 | }
225 |
226 | // Get the address of the specified property of the specified
227 | // object. Return 0x0000 if there is no such property.
228 | int getPropertyAddress(int obj,int prop)
229 | {
230 | int p;
231 | int s;
232 | int o;
233 | int pnum;
234 | int psize;
235 |
236 | // First, get the address of the property table for this
237 | // object.
238 | p = getPropertyList(obj);
239 |
240 | // Now step through, looking for the specified property.
241 | // Start by jumping over text header.
242 | o = memory.fetchByte(p);
243 | p = p + (o * 2) + 1;
244 |
245 | // Now we're at the start of the property table.
246 | s = memory.fetchByte(p);
247 | while (s != 0) {
248 | // Get the property number and the size of this property.
249 | if (version < 4) {
250 | pnum = (s & 0x1f);
251 | psize = ((s >> 5) & 0x07) + 1;
252 | }
253 | else {
254 | pnum = (s & 0x3f);
255 | if ((s & 0x80) == 0x80) {
256 | p++;
257 | psize = memory.fetchByte(p);
258 | psize = psize & 0x3f;
259 | }
260 | else
261 | psize = ((s >> 6) & 0x03) + 1;
262 | }
263 |
264 | // Step over the size byte
265 | p++;
266 |
267 | // If this is the correct property, return its address;
268 | // otherwise, step over the property and loop.
269 | if (pnum == prop)
270 | return p;
271 | else
272 | p = p + psize;
273 | s = memory.fetchByte(p);
274 | }
275 |
276 | // If we make it here, the property was not found.
277 | return 0;
278 | }
279 |
280 | // Get the first byte or word of a property--use the default
281 | // property if this one doesn't exist.
282 | int getProperty(int obj,int prop)
283 | {
284 | int pdata;
285 |
286 | // Attempt to get the address of this property for this
287 | // object.
288 | pdata = getPropertyAddress(obj,prop);
289 |
290 | // If the property exists, return it; otherwise, return
291 | // the appropriate value from the defaults table.
292 | if (pdata > 0) {
293 | if (getPropertyLength(pdata) == 1)
294 | return memory.fetchByte(pdata);
295 | else
296 | return memory.fetchWord(pdata);
297 | }
298 | else
299 | return memory.fetchWord(objTable + ((prop - 1) * 2));
300 | }
301 |
302 | // Return the property number of the property that follows
303 | // the specified property in the property list, or 0 if
304 | // the specified property doesn't exist.
305 | int getNextProperty(int obj,int prop)
306 | {
307 | int propaddr;
308 | int proplen;
309 | int propnum;
310 |
311 | // First, if the property number is 0, just return the
312 | // number of the first property of this object.
313 | if (prop == 0) {
314 | propaddr = getPropertyList(obj);
315 | // Skip over text-length byte and text header
316 | propaddr = propaddr + 1 + (memory.fetchByte(propaddr) * 2);
317 | // Return the number of the first property.
318 | // This will work if the property number is 0, too.
319 | if (version < 4)
320 | propnum = memory.fetchByte(propaddr) & 0x1f;
321 | else
322 | propnum = memory.fetchByte(propaddr) & 0x3f;
323 | return propnum;
324 | }
325 |
326 | // First, get the address of the specified property.
327 | // If it doesn't exist, return 0.
328 | propaddr = getPropertyAddress(obj,prop);
329 | if (propaddr == 0)
330 | return 0;
331 |
332 | // Now find out its length.
333 | proplen = getPropertyLength(propaddr);
334 |
335 | // Skip over the property data
336 | propaddr += proplen;
337 |
338 | // Now return the number of the next property. This will
339 | // return 0 if the property is a 0 byte.
340 | if (version < 4)
341 | propnum = memory.fetchByte(propaddr) & 0x1f;
342 | else
343 | propnum = memory.fetchByte(propaddr) & 0x3f;
344 | return propnum;
345 | }
346 |
347 | // Return the address of the Z-String containing the specified
348 | // object's name.
349 | int getObjectName(int obj)
350 | {
351 | int addr;
352 |
353 | addr = getPropertyList(obj) + 1;
354 | return addr;
355 | }
356 |
357 | // Put the specified value as the specified property of the
358 | // specified object.
359 | void putProperty(int obj,int prop,int value)
360 | {
361 | int propaddr;
362 | int proplen;
363 |
364 | // First, get the address of this property. Fail silently
365 | // if the property does not exist.
366 | propaddr = getPropertyAddress(obj,prop);
367 | if (propaddr == 0)
368 | return;
369 |
370 | // Now set the property, depending on its length.
371 | proplen = getPropertyLength(propaddr);
372 | if (proplen == 1)
373 | memory.putByte(propaddr,(value & 0xff));
374 | else
375 | memory.putWord(propaddr,value);
376 | }
377 |
378 |
379 | //////////////////////////////////////////////////////////////
380 | // Attribute manipulation routines
381 | //////////////////////////////////////////////////////////////
382 |
383 | // Return true if the specified object has the specified
384 | // attribute; otherwise return false.
385 | boolean hasAttribute(int obj,int attr)
386 | {
387 | int whichbyte;
388 | int whichbit;
389 | int bitmask;
390 | int attrbyte;
391 |
392 | // First, figure out which byte and bit we're looking at.
393 | whichbyte = attr / 8;
394 | whichbit = attr % 8;
395 |
396 | // Flip the bit number around to something we can use in
397 | // an AND.
398 | bitmask = 0x80 >>> whichbit;
399 |
400 | // Now get the appropriate byte and test it.
401 | attrbyte = memory.fetchByte(objTable + defaultsSize + ((obj - 1) * objEntrySize) + (whichbyte));
402 | if ((attrbyte & bitmask) == bitmask)
403 | return true;
404 | else
405 | return false;
406 | }
407 |
408 | // Set an attribute on an object.
409 | void setAttribute(int obj,int attr)
410 | {
411 | int whichbyte;
412 | int whichbit;
413 | int bitmask;
414 | int attrbyte;
415 |
416 | // First, figure out which byte and bit we're looking at.
417 | whichbyte = attr / 8;
418 | whichbit = attr % 8;
419 |
420 | // Flip the bit number around to something we can use in
421 | // an OR.
422 | bitmask = 0x80 >>> whichbit;
423 |
424 | // Now get the appropriate byte and set it.
425 | attrbyte = memory.fetchByte(objTable + defaultsSize + ((obj - 1) * objEntrySize) + (whichbyte));
426 | attrbyte = attrbyte | bitmask;
427 | memory.putByte((objTable + defaultsSize + ((obj - 1) * objEntrySize) + (whichbyte)),attrbyte);
428 | }
429 |
430 | // Clear an attribute on an object.
431 | void clearAttribute(int obj,int attr)
432 | {
433 | int whichbyte;
434 | int whichbit;
435 | int bitmask;
436 | int attrbyte;
437 |
438 | // First, figure out which byte and bit we're looking at.
439 | whichbyte = attr / 8;
440 | whichbit = attr % 8;
441 |
442 | // Flip the bit number around to something we can use in
443 | // a NOT.
444 | bitmask = 0x80 >>> whichbit;
445 |
446 | // Now get the appropriate byte and clear it.
447 | attrbyte = memory.fetchByte(objTable + defaultsSize + ((obj - 1) * objEntrySize) + (whichbyte));
448 | attrbyte = ((attrbyte & ~bitmask) & 0xff);
449 | memory.putByte((objTable + defaultsSize + ((obj - 1) * objEntrySize) + (whichbyte)),attrbyte);
450 | }
451 |
452 | }
453 |
--------------------------------------------------------------------------------
/src/main/java/com/zaxsoft/zmachine/ZCPU.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2008 Matthew E. Kimmel
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in
12 | * all copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | * THE SOFTWARE.
21 | */
22 | package com.zaxsoft.zmachine;
23 |
24 | import java.awt.*;
25 | import java.io.*;
26 | import java.util.Stack;
27 | import java.util.StringTokenizer;
28 | import java.util.Vector;
29 |
30 | /**
31 | * The ZCPU class implements the Central Processing Unit
32 | * of a ZMachine, and is the ZMachine's interface to the outside
33 | * world. With the assistance of other classes in the zmachine
34 | * package, and of a class supplied by the programmer that implements
35 | * the ZUserInterface interface, this class loads and executes
36 | * Z-code programs in the standard Infocom/Inform story-file format.
37 | *
38 | * @author Matt Kimmel
39 | */
40 | public class ZCPU extends Object implements Runnable {
41 | // Private constants
42 | // Opcode types
43 | private final int OPTYPE_0OP = 0; // 0OP opcode type
44 | private final int OPTYPE_1OP = 1; // 1OP opcode type
45 | private final int OPTYPE_2OP = 2; // 2OP opcode type
46 | private final int OPTYPE_VAR = 3; // Variable opcode type
47 | private final int OPTYPE_EXT = 4; // Extended opcode type
48 |
49 | // Argument types
50 | private final int ARGTYPE_BYTE = 0; // Byte
51 | private final int ARGTYPE_WORD = 1; // Word
52 |
53 | // Other objects associated with this ZMachine
54 | private ZMemory memory; // This ZMachine's memory
55 | private ZObjectTable objTable; // This ZMachine's object table
56 | private Stack callStack; // This ZMachine's call stack
57 | private ZRandom rndgen; // This ZMachine's random number generator
58 | private ZIOCard ioCard; // This ZMachine's I/O card
59 | private ZUserInterface zui; // User interface supplied to constructor
60 |
61 | // Private variables
62 | private String curStoryFile; // The storyfile we're using
63 | private int version = 0; // Version of the game we're playing.
64 | private int programScale; // Scaling factor for this program
65 | private ZCallFrame curCallFrame; // Current call frame
66 | private int curInstruction; // Instruction currently being executed
67 | private int curOpcode; // Opcode (untyped instruction) being executed
68 | private int curOpcodeType; // Type of current opcode;
69 | private int op1, op2, op1type, op2type; // Current operands for 1OPs and 2OPs and their types.
70 | private int[] vops = new int[8]; // Current operands for VARs and EXTs
71 | private int[] voptypes = new int[8]; // Type of current VAR/EXT operands
72 | private int numvops; // Number of operands for VARs and EXTs
73 | private int curBranch; // Current branch argument
74 | private boolean curBranchReversed; // Current branch logic reversed?
75 | private int curResult; // Current result argument
76 | private String curString; // Current string argument
77 | private boolean decode_ret_flag = false; // Set to true when decodeLoop must return
78 | private int ret_value; // Value from last RET instruction, if returning from interrupt
79 | private int abbrevTable; // Location in memory of abbreviation table.
80 | private int globalVars; // Location in memory of global variables
81 | private int dynamicMemorySize; // Size of dynamic memory
82 | private boolean restartFlag; // true if this is a restart
83 | private int mainDictionary; // Address of main dictionary
84 | private byte[] undoState; // Current undo state
85 | private boolean did_newline = false; // Set to true whenever NEW_LINE called--used by READ
86 |
87 | // Default alphabets for decoding Z-Strings
88 | private boolean altCharSet = false; // True if we're using an alternate character set
89 | private int alphabetL = 0;
90 | private int alphabetU = 1;
91 | private int alphabetP = 2; // Set to 3 in V1
92 | private char[][] alphabet = {
93 | { ' ','\0','\0','\0','\0','\0','a','b','c','d','e','f','g','h','i',
94 | 'j','k','l','m','n','o','p','q','r','s','t','u','v','w',
95 | 'x','y','z' },
96 | { ' ','\0','\0','\0','\0','\0','A','B','C','D','E','F','G','H','I',
97 | 'J','K','L','M','N','O','P','Q','R','S','T','U','V','W',
98 | 'X','Y','Z' },
99 | { ' ','\0','\0','\0','\0','\0','\0','\n','0','1','2','3','4','5','6','7',
100 | '8','9','.',',','!','?','_','#','\'','\"','/','\\','-',
101 | ':','(',')'},
102 | { ' ','\0','\0','\0','\0','\0','\0','0','1','2','3','4','5','6','7',
103 | '8','9','.',',','!','?','_','#','\'','\"','/','\\','<','-',
104 | ':','(',')'}
105 | };
106 |
107 | // The constructor takes an object that implements the
108 | // ZUserInterface interface as an argument, and initializes
109 | // various variables and objects (but does not load or start
110 | // a game).
111 | public ZCPU(ZUserInterface ui)
112 | {
113 | zui = ui;
114 | memory = new ZMemory();
115 | callStack = new Stack();
116 | rndgen = new ZRandom();
117 | ioCard = new ZIOCard();
118 | objTable = new ZObjectTable();
119 | }
120 |
121 | // The initialize method does several things: loads a game;
122 | // calls the initialize methods of the other objects; modifies
123 | // IROM as appropriate to the capabilities of the ZMachine and
124 | // the ZUserInterface.
125 | public void initialize(String storyFile)
126 | {
127 | int i;
128 | boolean transcriptOn = false;
129 | Dimension s;
130 | int termChars;
131 |
132 | // If this is a restart, remember the value of the printer
133 | // transcript bit.
134 | if (restartFlag) {
135 | if ((memory.fetchWord(0x10) & 0x01) == 0x01)
136 | transcriptOn = true;
137 | else
138 | transcriptOn = false;
139 | }
140 |
141 | // First, initialize all of the objects. For the ZMemory
142 | // object, this includes loading the game file.
143 | curStoryFile = storyFile;
144 | memory.initialize(zui,storyFile);
145 | version = memory.fetchByte(0x00);
146 | if ((version < 1) || (version > 8) || (version == 6))
147 | zui.fatal("Unsupported storyfile version: " +
148 | String.valueOf(version) + ".");
149 | zui.initialize(version);
150 | rndgen.initialize(zui);
151 | ioCard.initialize(zui,memory,version,true);
152 | objTable.initialize(zui,memory,version);
153 |
154 | // Get the program scale
155 | if (version <= 3)
156 | programScale = 2;
157 | else if ((version == 4) || (version == 5))
158 | programScale = 4;
159 | else
160 | programScale = 8;
161 |
162 | // If this is a V1 game, we need to use the V1 P alhabet.
163 | if (version == 1)
164 | alphabetP = 3;
165 |
166 | // Now do necessary modifications to the IROM.
167 | // First the byte at 0x01 (the only byte used in V1-3).
168 | i = memory.fetchByte(0x01);
169 |
170 | // Set bits at 0x01 for V1-3 games
171 | if (version <= 3) {
172 | i = i & ~0x08; // Tandy bit off
173 | if (zui.hasStatusLine())
174 | i = i & ~0x10; // Status line not not available
175 | else
176 | i = i | 0x10; // Status line not available
177 | if (zui.hasUpperWindow())
178 | i = i | 0x20; // Upper window available
179 | else
180 | i = i & ~0x20; // Upper window not available
181 | if (zui.defaultFontProportional())
182 | i = i | 0x40; // Default font is proportional
183 | else
184 | i = i & ~0x40; // Default font is fixed-width
185 | }
186 | else { // Set bits for V4+ games
187 | if ((version >= 5) && (zui.hasColors()))
188 | i = i | 0x01;
189 | // V6 picture bit at bit 1
190 | if (zui.hasBoldface())
191 | i = i | 0x04;
192 | if (zui.hasItalic())
193 | i = i | 0x08;
194 | if (zui.hasFixedWidth())
195 | i = i | 0x10;
196 | // V6 sound bit at bit 5
197 | if (zui.hasTimedInput())
198 | i = i | 0x80;
199 | }
200 | memory.putByte(0x01,i);
201 |
202 | // In V4+, set various other IROM bytes
203 | if (version >= 4) {
204 | memory.putByte(0x1e,6); // We'll say we're an MS-DOS interpreter
205 | memory.putByte(0x1f,(byte)'A'); // Interpreter version
206 |
207 | // Screen height and width in characters
208 | s = zui.getScreenCharacters();
209 | memory.putByte(0x20,s.height);
210 | memory.putByte(0x21,s.width);
211 |
212 | // Screen height and width in units, font size in units, colors (V5+)
213 | if (version >= 5) {
214 | s = zui.getScreenUnits();
215 | memory.putWord(0x22,s.width);
216 | memory.putWord(0x24,s.height);
217 | s = zui.getFontSize();
218 | memory.putByte(0x26,s.height);
219 | memory.putByte(0x27,s.width);
220 | memory.putByte(0x2c,zui.getDefaultBackground());
221 | memory.putByte(0x2d,zui.getDefaultForeground());
222 | }
223 | }
224 |
225 | // If we're restarting, restore the printer transcript bit
226 | if (restartFlag) {
227 | i = memory.fetchWord(0x10);
228 | if (transcriptOn)
229 | i = i | 0x01;
230 | else
231 | i = i & ~0x01;
232 | memory.putWord(0x10,i);
233 | restartFlag = false; // From here on, it's a new program
234 | }
235 |
236 | // Get the location of the abbreviation table
237 | if (version > 1)
238 | abbrevTable = memory.fetchWord(0x18);
239 |
240 | // Get the location of the global variable table
241 | globalVars = memory.fetchWord(0x0c);
242 |
243 | // Get the location of the main dictionary
244 | mainDictionary = memory.fetchWord(0x08);
245 |
246 | // Get size of dynamic memory
247 | dynamicMemorySize = memory.fetchWord(0x0e);
248 |
249 | // Get any additional terminating characters, and pass them to
250 | // the user interface. (V5+)
251 | if (version >= 5) {
252 | termChars = memory.fetchWord(0x2e);
253 | if (termChars != 0) {
254 | i = 0;
255 | int tc = memory.fetchByte(termChars);
256 | Vector terminators = new Vector();
257 | while (tc != 0) {
258 | terminators.addElement(new Integer(tc));
259 | i++;
260 | tc = memory.fetchByte(termChars+i);
261 | }
262 | zui.setTerminatingCharacters(terminators);
263 | }
264 | }
265 |
266 | // Get the alternate character set, if there is one,
267 | // in V5+ games. (currently not implemented)
268 | }
269 |
270 | // The start method starts execution of the story-file as a separate thread.
271 | public Thread start()
272 | {
273 | Thread execThread;
274 |
275 | // If the version number is 0, then we haven't loaded
276 | // a storyfile yet. For now, this just means an immediate
277 | // return.
278 | if (version == 0)
279 | return null;
280 |
281 | // Otherwise, start a new thread, which starts with this object's run()
282 | // method, and return a handle to the thread.
283 | execThread = new Thread(this,"ZMachine");
284 | execThread.start();
285 |
286 | return execThread;
287 | }
288 |
289 | // This method is called when the ZMachine thread is started.
290 | public void run()
291 | {
292 | do {
293 | // Reinitialize if this is a restart
294 | if (restartFlag) {
295 | initialize(curStoryFile);
296 | restartFlag = false;
297 | }
298 |
299 | // Create an initial call-stack frame
300 | curCallFrame = new ZCallFrame();
301 | curCallFrame.pc = memory.fetchWord(0x06);
302 | curCallFrame.routineStack = new Stack();
303 | curCallFrame.numLocalVars = 0;
304 | curCallFrame.callType = ZCallFrame.INTERRUPT; // This should never be examined.
305 | curCallFrame.argCount = 0;
306 | curCallFrame.frameNumber = 0;
307 | callStack = new Stack();
308 |
309 | // Now start executing code. The
310 | // return--if it does, we'll just return as well.
311 | decodeLoop();
312 | } while (restartFlag);
313 |
314 | return;
315 | }
316 |
317 | // This is the main loop of the ZMachine. It decodes instructions
318 | // and executes them. It may be called recursively during
319 | // interrupts.
320 | private void decodeLoop()
321 | {
322 | int v;
323 | int typebyte;
324 | int maxops;
325 | boolean done;
326 |
327 | while (true) { // Decode in an endless loop
328 | // Grab the opcode, adjust the PC accordingly. It is
329 | // up to the implementation of each instruction to get
330 | // its own result, branch and string arguments using
331 | // utility functions.
332 | // System.out.println(Integer.toHexString(curCallFrame.pc)); //db
333 | curInstruction = memory.fetchByte(curCallFrame.pc);
334 | curCallFrame.pc++;
335 |
336 | ///////////////////////////////////////////////////////////
337 | // Get the operands for this instruction, and type it.
338 | ///////////////////////////////////////////////////////////
339 | if ((curInstruction >= 0x00) && (curInstruction <= 0x7f)) {
340 | // A non-variable 2OP.
341 | // Get first operand
342 | if ((curInstruction & 0x40) == 0x40) { // A variable number
343 | v = memory.fetchByte(curCallFrame.pc);
344 | op1 = getVariable(v);
345 | op1type = ARGTYPE_WORD;
346 | }
347 | else { // A byte constant
348 | op1 = memory.fetchByte(curCallFrame.pc);
349 | op1type = ARGTYPE_BYTE;
350 | }
351 | curCallFrame.pc++;
352 |
353 | // Get second operand
354 | if ((curInstruction & 0x20) == 0x20) { // A variable number
355 | v = memory.fetchByte(curCallFrame.pc);
356 | op2 = getVariable(v);
357 | op2type = ARGTYPE_WORD;
358 | }
359 | else { // A byte constant
360 | op2 = memory.fetchByte(curCallFrame.pc);
361 | op2type = ARGTYPE_BYTE;
362 | }
363 | curCallFrame.pc++;
364 |
365 | curOpcodeType = OPTYPE_2OP;
366 | curOpcode = (curInstruction & 0x1f);
367 | }
368 | else if ((curInstruction >= 0x80) && (curInstruction <= 0xaf)) {
369 | // A 1OP.
370 | switch (curInstruction & 0x30) {
371 | case 0x00 : // A word constant
372 | op1 = memory.fetchWord(curCallFrame.pc);
373 | op1type = ARGTYPE_WORD;
374 | curCallFrame.pc += 2;
375 | break;
376 | case 0x10 : // A byte constant
377 | op1 = memory.fetchByte(curCallFrame.pc);
378 | op1type = ARGTYPE_BYTE;
379 | curCallFrame.pc++;
380 | break;
381 | case 0x20 : // A variable
382 | v = memory.fetchByte(curCallFrame.pc);
383 | op1 = getVariable(v);
384 | op1type = ARGTYPE_WORD;
385 | curCallFrame.pc++;
386 | break;
387 | }
388 |
389 | curOpcodeType = OPTYPE_1OP;
390 | curOpcode = (curInstruction & 0x0f);
391 | }
392 | else if ((curInstruction >= 0xb0) && (curInstruction <= 0xbf) &&
393 | (curInstruction != 0xbe)) {
394 | // A 0OP.
395 | curOpcodeType = OPTYPE_0OP;
396 | curOpcode = (curInstruction & 0x0f);
397 | }
398 | else if ((curInstruction >= 0xc0) && (curInstruction <= 0xdf) &&
399 | (curInstruction != 0xc1)) {
400 | // A variable 2OP.
401 | // Get the type byte.
402 | typebyte = memory.fetchByte(curCallFrame.pc);
403 | curCallFrame.pc++;
404 |
405 | // Get the first operand
406 | switch (typebyte & 0xc0) {
407 | case 0x00 : // Word constant
408 | op1 = memory.fetchWord(curCallFrame.pc);
409 | op1type = ARGTYPE_WORD;
410 | curCallFrame.pc += 2;
411 | break;
412 | case 0x40 : // A byte constant
413 | op1 = memory.fetchByte(curCallFrame.pc);
414 | op1type = ARGTYPE_BYTE;
415 | curCallFrame.pc++;
416 | break;
417 | case 0x80 : // A variable
418 | v = memory.fetchByte(curCallFrame.pc);
419 | op1 = getVariable(v);
420 | op1type = ARGTYPE_WORD;
421 | curCallFrame.pc++;
422 | break;
423 | case 0xc0 : // An error
424 | zui.fatal("Error: Variable 2OP with no ops.");
425 | }
426 |
427 | // Get the second operand
428 | switch (typebyte & 0x30) {
429 | case 0x00 : // Word constant
430 | op2 = memory.fetchWord(curCallFrame.pc);
431 | op2type = ARGTYPE_WORD;
432 | curCallFrame.pc += 2;
433 | break;
434 | case 0x10 : // A byte constant
435 | op2 = memory.fetchByte(curCallFrame.pc);
436 | op2type = ARGTYPE_BYTE;
437 | curCallFrame.pc++;
438 | break;
439 | case 0x20 : // A variable
440 | v = memory.fetchByte(curCallFrame.pc);
441 | op2 = getVariable(v);
442 | op2type = ARGTYPE_WORD;
443 | curCallFrame.pc++;
444 | break;
445 | case 0x30 : // An error
446 | zui.fatal("Error: Variable 2OP with one op.");
447 | }
448 |
449 | curOpcodeType = OPTYPE_2OP;
450 | curOpcode = (curInstruction & 0x1f);
451 | }
452 | else if (((curInstruction >= 0xe0) && (curInstruction <= 0xff)) ||
453 | (curInstruction == 0xc1)) {
454 | // Variable instruction, or 0xc1 (JE with up to 4 operands)
455 | // Get the operands
456 | numvops = 0;
457 | if ((curInstruction == 0xec) || (curInstruction == 0xfa)) {
458 | // Double-variables
459 | typebyte = memory.fetchWord(curCallFrame.pc);
460 | curCallFrame.pc += 2;
461 | maxops = 8;
462 | }
463 | else {
464 | typebyte = memory.fetchByte(curCallFrame.pc);
465 | curCallFrame.pc++;
466 | maxops = 4;
467 | }
468 | done = false;
469 | for (int i=0;((i> ((maxops-1-i)*2)) & 0x03) {
471 | case 0x00 : // Word constant
472 | vops[i] = memory.fetchWord(curCallFrame.pc);
473 | voptypes[i] = ARGTYPE_WORD;
474 | curCallFrame.pc += 2;
475 | numvops++;
476 | break;
477 | case 0x01 : // Byte constant
478 | vops[i] = memory.fetchByte(curCallFrame.pc);
479 | voptypes[i] = ARGTYPE_BYTE;
480 | curCallFrame.pc++;
481 | numvops++;
482 | break;
483 | case 0x02 : // A variable
484 | v = memory.fetchByte(curCallFrame.pc);
485 | vops[i] = getVariable(v);
486 | voptypes[i] = ARGTYPE_WORD;
487 | curCallFrame.pc++;
488 | numvops++;
489 | break;
490 | case 0x03 : // End of arguments
491 | done = true;
492 | break;
493 | }
494 | }
495 |
496 | if (curInstruction == 0xc1) {
497 | curOpcodeType = OPTYPE_2OP;
498 | curOpcode = 0x01;
499 | }
500 | else {
501 | curOpcodeType = OPTYPE_VAR;
502 | curOpcode = (curInstruction & 0x1f);
503 | }
504 | }
505 | else if (curInstruction == 0xbe) {
506 | // Extended instruction. Decode similarly to a variable instruction.
507 | curOpcodeType = OPTYPE_EXT;
508 | curOpcode = memory.fetchByte(curCallFrame.pc);
509 | curCallFrame.pc++;
510 |
511 | numvops = 0;
512 | typebyte = memory.fetchByte(curCallFrame.pc);
513 | curCallFrame.pc++;
514 | done = false;
515 | for (int i=0;((i<4) && (!done));i++) {
516 | switch ((typebyte >> ((3-i)*2)) & 0x03) {
517 | case 0x00 : // Word constant
518 | vops[i] = memory.fetchWord(curCallFrame.pc);
519 | voptypes[i] = ARGTYPE_WORD;
520 | curCallFrame.pc += 2;
521 | numvops++;
522 | break;
523 | case 0x01 : // Byte constant
524 | vops[i] = memory.fetchByte(curCallFrame.pc);
525 | voptypes[i] = ARGTYPE_BYTE;
526 | curCallFrame.pc++;
527 | numvops++;
528 | break;
529 | case 0x02 : // A variable
530 | v = memory.fetchByte(curCallFrame.pc);
531 | vops[i] = getVariable(v);
532 | voptypes[i] = ARGTYPE_WORD;
533 | curCallFrame.pc++;
534 | numvops++;
535 | break;
536 | case 0x03 : // End of arguments
537 | done = true;
538 | break;
539 | }
540 | }
541 | }
542 | else
543 | // This should never happen.
544 | zui.fatal("Malformed instruction: " + curInstruction);
545 |
546 | ///////////////////////////////////////////////////////////
547 | // Dispatch the instruction.
548 | ///////////////////////////////////////////////////////////
549 | if (curOpcodeType == OPTYPE_0OP) {
550 | // 0OP opcodes.
551 | switch (curOpcode) {
552 | case 0x00 : zop_rtrue();
553 | break;
554 | case 0x01 : zop_rfalse();
555 | break;
556 | case 0x02 : getString();
557 | zop_print();
558 | break;
559 | case 0x03 : getString();
560 | zop_print_rtrue();
561 | break;
562 | case 0x04 : zop_nop();
563 | break;
564 | case 0x05 : if (version < 4)
565 | getBranch();
566 | else if (version == 4)
567 | getResult();
568 | else
569 | zui.fatal("SAVE 0OP unsupported after version 4.");
570 | zop_save();
571 | break;
572 | case 0x06 : if (version < 4)
573 | getBranch();
574 | else if (version == 4)
575 | getResult();
576 | else
577 | zui.fatal("RESTORE 0OP unsupported after version 4.");
578 | zop_restore();
579 | break;
580 | case 0x07 : zop_restart();
581 | break;
582 | case 0x08 : zop_ret_pulled();
583 | break;
584 | case 0x09 : if (version < 5)
585 | zop_pop();
586 | else {
587 | getResult();
588 | zop_catch();
589 | }
590 | break;
591 | case 0x0a : zop_quit();
592 | break;
593 | case 0x0b : zop_new_line();
594 | break;
595 | case 0x0c : zop_show_status();
596 | break;
597 | case 0x0d : getBranch();
598 | zop_verify();
599 | break;
600 | case 0x0e : // Start of extended instruction
601 | zui.fatal("Found opcode 0xBE in 0OP dispatcher");
602 | case 0x0f : getBranch();
603 | zop_piracy();
604 | break;
605 | default : zui.fatal("Unknown 0OP - probably a bug.");
606 | }
607 | }
608 | else if (curOpcodeType == OPTYPE_1OP) {
609 | // 1OP opcodes
610 | switch (curOpcode) {
611 | case 0x00 : getBranch();
612 | zop_jz();
613 | break;
614 | case 0x01 : getResult();
615 | getBranch();
616 | zop_get_sibling();
617 | break;
618 | case 0x02 : getResult();
619 | getBranch();
620 | zop_get_child();
621 | break;
622 | case 0x03 : getResult();
623 | zop_get_parent();
624 | break;
625 | case 0x04 : getResult();
626 | zop_get_prop_len();
627 | break;
628 | case 0x05 : zop_inc();
629 | break;
630 | case 0x06 : zop_dec();
631 | break;
632 | case 0x07 : zop_print_addr();
633 | break;
634 | case 0x08 : getResult();
635 | zop_call_f0();
636 | break;
637 | case 0x09 : zop_remove_obj();
638 | break;
639 | case 0x0a : zop_print_obj();
640 | break;
641 | case 0x0b : zop_ret();
642 | break;
643 | case 0x0c : zop_jump();
644 | break;
645 | case 0x0d : zop_print_paddr();
646 | break;
647 | case 0x0e : getResult();
648 | zop_load();
649 | break;
650 | case 0x0f : if (version < 5) {
651 | getResult();
652 | zop_not();
653 | }
654 | else
655 | zop_call_p0();
656 | break;
657 | default : zui.fatal("Unknown 1OP - probably a bug.");
658 | }
659 | }
660 | else if (curOpcodeType == OPTYPE_2OP) {
661 | // 2OP opcodes
662 | switch (curOpcode) {
663 | case 0x00 : zui.fatal("Unspecified instruction: " + curInstruction);
664 | case 0x01 : getBranch();
665 | zop_je();
666 | break;
667 | case 0x02 : getBranch();
668 | zop_jl();
669 | break;
670 | case 0x03 : getBranch();
671 | zop_jg();
672 | break;
673 | case 0x04 : getBranch();
674 | zop_dec_jl();
675 | break;
676 | case 0x05 : getBranch();
677 | zop_inc_jg();
678 | break;
679 | case 0x06 : getBranch();
680 | zop_jin();
681 | break;
682 | case 0x07 : getBranch();
683 | zop_test();
684 | break;
685 | case 0x08 : getResult();
686 | zop_or();
687 | break;
688 | case 0x09 : getResult();
689 | zop_and();
690 | break;
691 | case 0x0a : getBranch();
692 | zop_test_attr();
693 | break;
694 | case 0x0b : zop_set_attr();
695 | break;
696 | case 0x0c : zop_clear_attr();
697 | break;
698 | case 0x0d : zop_store();
699 | break;
700 | case 0x0e : zop_insert_obj();
701 | break;
702 | case 0x0f : getResult();
703 | zop_loadw();
704 | break;
705 | case 0x10 : getResult();
706 | zop_loadb();
707 | break;
708 | case 0x11 : getResult();
709 | zop_get_prop();
710 | break;
711 | case 0x12 : getResult();
712 | zop_get_prop_addr();
713 | break;
714 | case 0x13 : getResult();
715 | zop_get_next_prop();
716 | break;
717 | case 0x14 : getResult();
718 | zop_add();
719 | break;
720 | case 0x15 : getResult();
721 | zop_sub();
722 | break;
723 | case 0x16 : getResult();
724 | zop_mul();
725 | break;
726 | case 0x17 : getResult();
727 | zop_div();
728 | break;
729 | case 0x18 : getResult();
730 | zop_mod();
731 | break;
732 | case 0x19 : getResult();
733 | zop_call_f1();
734 | break;
735 | case 0x1a : zop_call_p1();
736 | break;
737 | case 0x1b : zop_set_colour();
738 | break;
739 | case 0x1c : zop_throw();
740 | break;
741 | case 0x1d :
742 | case 0x1e :
743 | case 0x1f : zui.fatal("Unspecified instruction: " + curInstruction);
744 | default : zui.fatal("Unknown 2OP. Probably a bug.");
745 | }
746 | }
747 | else if (curOpcodeType == OPTYPE_VAR) {
748 | // VAR instructions
749 | switch (curOpcode) {
750 | case 0x00 : getResult();
751 | zop_call_fv();
752 | break;
753 | case 0x01 : zop_storew();
754 | break;
755 | case 0x02 : zop_storeb();
756 | break;
757 | case 0x03 : zop_put_prop();
758 | break;
759 | case 0x04 : if (version >= 5)
760 | getResult();
761 | zop_read();
762 | break;
763 | case 0x05 : zop_print_char();
764 | break;
765 | case 0x06 : zop_print_num();
766 | break;
767 | case 0x07 : getResult();
768 | zop_random();
769 | break;
770 | case 0x08 : zop_push();
771 | break;
772 | case 0x09 : zop_pull();
773 | break;
774 | case 0x0a : zop_split_screen();
775 | break;
776 | case 0x0b : zop_set_window();
777 | break;
778 | case 0x0c : getResult();
779 | zop_call_fd();
780 | break;
781 | case 0x0d : zop_erase_window();
782 | break;
783 | case 0x0e : zop_erase_line();
784 | break;
785 | case 0x0f : zop_set_cursor();
786 | break;
787 | case 0x10 : zop_get_cursor();
788 | break;
789 | case 0x11 : zop_set_text_style();
790 | break;
791 | case 0x12 : zop_buffer_mode();
792 | break;
793 | case 0x13 : zop_output_stream();
794 | break;
795 | case 0x14 : zop_input_stream();
796 | break;
797 | case 0x15 : zop_sound();
798 | break;
799 | case 0x16 : getResult();
800 | zop_read_char();
801 | break;
802 | case 0x17 : getResult();
803 | getBranch();
804 | zop_scan_table();
805 | break;
806 | case 0x18 : getResult();
807 | op1 = vops[0];
808 | op1type = voptypes[0];
809 | zop_not();
810 | break;
811 | case 0x19 : zop_call_pv();
812 | break;
813 | case 0x1a : zop_call_pv();
814 | break;
815 | case 0x1b : zop_tokenise();
816 | break;
817 | case 0x1c : zop_encode_text();
818 | break;
819 | case 0x1d : zop_copy_table();
820 | break;
821 | case 0x1e : zop_print_table();
822 | break;
823 | case 0x1f : getBranch();
824 | zop_check_arg_count();
825 | break;
826 | default : zui.fatal("Unknown VAR - probably a bug.");
827 | }
828 | }
829 | else if (curOpcodeType == OPTYPE_EXT) {
830 | // Extended instructions
831 | switch (curOpcode) {
832 | case 0x00 : getResult();
833 | zop_ext_save();
834 | break;
835 | case 0x01 : getResult();
836 | zop_ext_restore();
837 | break;
838 | case 0x02 : getResult();
839 | zop_log_shift();
840 | break;
841 | case 0x03 : getResult();
842 | zop_art_shift();
843 | break;
844 | case 0x04 : getResult();
845 | zop_set_font();
846 | break;
847 | case 0x05 : zop_draw_picture();
848 | break;
849 | case 0x06 : getBranch();
850 | zop_picture_data();
851 | break;
852 | case 0x07 : zop_erase_picture();
853 | break;
854 | case 0x08 : zop_set_margins();
855 | break;
856 | case 0x09 : getResult();
857 | zop_save_undo();
858 | break;
859 | case 0x0a : getResult();
860 | zop_restore_undo();
861 | break;
862 | case 0x0b :
863 | case 0x0c :
864 | case 0x0d :
865 | case 0x0e :
866 | case 0x0f : zui.fatal("Unspecified EXT instruction: " + curOpcode);
867 | case 0x10 : zop_move_window();
868 | break;
869 | case 0x11 : zop_window_size();
870 | break;
871 | case 0x12 : zop_window_style();
872 | break;
873 | case 0x13 : getResult();
874 | zop_get_wind_prop();
875 | break;
876 | case 0x14 : zop_scroll_window();
877 | break;
878 | case 0x15 : zop_pop_stack();
879 | break;
880 | case 0x16 : zop_read_mouse();
881 | break;
882 | case 0x17 : zop_mouse_window();
883 | break;
884 | case 0x18 : getBranch();
885 | zop_push_stack();
886 | break;
887 | case 0x19 : zop_put_wind_prop();
888 | break;
889 | case 0x1a : zop_print_form();
890 | break;
891 | case 0x1b : getBranch();
892 | zop_make_menu();
893 | break;
894 | case 0x1c : zop_picture_table();
895 | break;
896 | default : zui.fatal("Unspecified EXT instruction: " + curOpcode);
897 | }
898 | }
899 | else
900 | zui.fatal("Unknown instruction: " + curInstruction);
901 |
902 | if (decode_ret_flag) {
903 | // An instruction has indicated that this decodeLoop
904 | // should return.
905 | decode_ret_flag = false;
906 | return;
907 | }
908 |
909 | if (restartFlag)
910 | return; // Also return during a restart
911 | }
912 | }
913 |
914 |
915 | ///////////////////////////////////////////////////////////////////
916 | // Utility functions
917 | ///////////////////////////////////////////////////////////////////
918 |
919 | // This method gets the argument of the current
920 | // instruction. It stores the value of the argument in the
921 | // global variable curBranch, and sets curBranchReversed to
922 | // true if the logic of the branch is reversed. curCallFrame.pc
923 | // should be pointing at the argument when this is called;
924 | // it is adjusted accordingly.
925 | private void getBranch()
926 | {
927 | int b1, b2;
928 | int sval;
929 |
930 | // Get the first byte of the branch
931 | b1 = memory.fetchByte(curCallFrame.pc);
932 | curCallFrame.pc++;
933 |
934 | // Check to see if logic is reversed
935 | if ((b1 & 0x80) == 0x80)
936 | curBranchReversed = false;
937 | else
938 | curBranchReversed = true;
939 |
940 | // If the branch is only one byte long, just set its
941 | // value and return.
942 | if ((b1 & 0x40) == 0x40) {
943 | curBranch = (b1 & 0x3f);
944 | return;
945 | }
946 |
947 | // Otherwise, construct a signed branch value.
948 | b2 = memory.fetchByte(curCallFrame.pc);
949 | curCallFrame.pc++;
950 | sval = (((((b1 & 0x3f) << 8) & 0x3f00) | b2) & 0x3fff);
951 | // If the following makes no sense, see the Z-Machine spec
952 | // on signed numbers.
953 | if ((sval & 0x2000) == 0x2000)
954 | curBranch = (sval - 16384);
955 | else
956 | curBranch = sval;
957 | }
958 |
959 | // Do a branch, based on the values of curBranch and
960 | // curBranchReversed.
961 | private void doBranch()
962 | {
963 | if (curBranchReversed)
964 | return;
965 | else {
966 | switch (curBranch) {
967 | case 0 : zop_rfalse();
968 | break;
969 | case 1 : zop_rtrue();
970 | break;
971 | default : curCallFrame.pc = curCallFrame.pc + curBranch - 2;
972 | break;
973 | }
974 | return;
975 | }
976 | }
977 |
978 | // Don't do a branch, based on the values of curBranch and
979 | // curBranchReversed. (If curBranchReversed is true, this
980 | // implies a branch).
981 | private void dontBranch()
982 | {
983 | if (curBranchReversed) {
984 | switch (curBranch) {
985 | case 0 : zop_rfalse();
986 | break;
987 | case 1 : zop_rtrue();
988 | break;
989 | default : curCallFrame.pc = curCallFrame.pc + curBranch - 2;
990 | break;
991 | }
992 | return;
993 | }
994 | else
995 | return;
996 | }
997 |
998 | // This method gets the argument of the current
999 | // instruction and stores it in the global variable curResult.
1000 | // curCallFrame.pc should be pointing at the argument when
1001 | // this is called; it is adjusted accordingly.
1002 | private void getResult()
1003 | {
1004 | curResult = memory.fetchByte(curCallFrame.pc);
1005 | curCallFrame.pc++;
1006 | }
1007 |
1008 | // This method gets the argument of the current
1009 | // instruction, decodes it, and stores it in the global variable
1010 | // curString. curCallFrame.pc should be pointing at the start
1011 | // of the string; it is adjusted accordingly.
1012 | private void getString()
1013 | {
1014 | int w;
1015 |
1016 | // First, decode the string
1017 | curString = decodeZString(curCallFrame.pc);
1018 |
1019 | // Now, adjust the PC.
1020 | w = memory.fetchWord(curCallFrame.pc);
1021 | curCallFrame.pc += 2;
1022 | while ((w & 0x8000) == 0) {
1023 | w = memory.fetchWord(curCallFrame.pc);
1024 | curCallFrame.pc += 2;
1025 | }
1026 | }
1027 |
1028 | // This function decodes the Z-String at the specified
1029 | // address, and returns it as a Java String object.
1030 | private String decodeZString(int addr)
1031 | {
1032 | StringBuffer decodedstr = new StringBuffer();
1033 | int w, tmpaddr;
1034 | int currentAlphabet, lockAlphabet;
1035 | int abbrevAddr;
1036 | char c, c2, c3;
1037 | int zlen, curindex;
1038 | int[] zchars;
1039 |
1040 | // First, throw all of the Z-characters, unprocessed, into
1041 | // an array for easy access.
1042 | // First, count the zcharacters.
1043 | tmpaddr = addr;
1044 | zlen = 0;
1045 | do {
1046 | w = memory.fetchWord(tmpaddr);
1047 | tmpaddr += 2;
1048 | zlen += 3;
1049 | } while ((w & 0x8000) != 0x8000);
1050 | // Then, allocate an array and put them in.
1051 | zchars = new int[zlen];
1052 | curindex = 0;
1053 | tmpaddr = addr;
1054 | w = memory.fetchWord(tmpaddr);
1055 | tmpaddr += 2;
1056 | zchars[curindex] = ((w >> 10) & 0x1f);
1057 | zchars[curindex+1] = ((w >> 5) & 0x1f);
1058 | zchars[curindex+2] = (w & 0x1f);
1059 | curindex += 3;
1060 | while ((w & 0x8000) == 0) {
1061 | w = memory.fetchWord(tmpaddr);
1062 | tmpaddr += 2;
1063 | zchars[curindex] = ((w >> 10) & 0x1f);
1064 | zchars[curindex+1] = ((w >> 5) & 0x1f);
1065 | zchars[curindex+2] = (w & 0x1f);
1066 | curindex += 3;
1067 | }
1068 |
1069 | // Now, decode the sequence of Z-characters.
1070 | c = 0;
1071 | c2 = 0;
1072 | c3 = 0;
1073 | currentAlphabet = alphabetL;
1074 | lockAlphabet = alphabetL;
1075 | for (int i=0;i < zlen;i++) {
1076 | c = (char)zchars[i];
1077 | // Decode character -- handle special characters as
1078 | // necessary. A bit of code is repeated here for
1079 | // the sake of cutting down on the number of comparisons.
1080 | switch (c) {
1081 | case 1 : if (version == 1) { // Newline in V1
1082 | decodedstr.append("\n");
1083 | currentAlphabet = lockAlphabet;
1084 | }
1085 | else { // Abbreviation in V2+
1086 | i++;
1087 | if (i >= zlen) // This is all we're getting.
1088 | break;
1089 | c2 = (char)zchars[i];
1090 | abbrevAddr = memory.fetchWord(abbrevTable + (((((int)c) - 1) * 32 + ((int)c2)) * 2));
1091 | abbrevAddr *= 2; // Word address
1092 | decodedstr.append(decodeZString(abbrevAddr));
1093 | }
1094 | break;
1095 | case 2 : if (version <= 2) { // Shift up
1096 | if (currentAlphabet == alphabetP)
1097 | currentAlphabet = alphabetL;
1098 | else
1099 | currentAlphabet++;
1100 | }
1101 | else { // An abbreviation
1102 | i++;
1103 | if (i >= zlen)
1104 | break;
1105 | c2 = (char)zchars[i];
1106 | abbrevAddr = memory.fetchWord(abbrevTable + (((((int)c) - 1) * 32 + ((int)c2)) * 2));
1107 | abbrevAddr *= 2; // Word address
1108 | decodedstr.append(decodeZString(abbrevAddr));
1109 | }
1110 | break;
1111 | case 3 : if (version <= 2) { // Shift down
1112 | if (currentAlphabet == alphabetL)
1113 | currentAlphabet = alphabetP;
1114 | else if (currentAlphabet == alphabetP)
1115 | currentAlphabet = alphabetU;
1116 | else
1117 | currentAlphabet = alphabetL;
1118 | }
1119 | else { // Abbreviation
1120 | i++;
1121 | if (i >= zlen)
1122 | break;
1123 | c2 = (char)zchars[i];
1124 | abbrevAddr = memory.fetchWord(abbrevTable + (((((int)c) - 1) * 32 + ((int)c2)) * 2));
1125 | abbrevAddr *= 2; // Word address
1126 | decodedstr.append(decodeZString(abbrevAddr));
1127 | }
1128 | break;
1129 | case 4 : // Always a shift up
1130 | if (currentAlphabet == alphabetP)
1131 | currentAlphabet = alphabetL;
1132 | else
1133 | currentAlphabet++;
1134 | if (version <= 2)
1135 | lockAlphabet = currentAlphabet;
1136 | break;
1137 | case 5 : // Always a shift down
1138 | if (currentAlphabet == alphabetL)
1139 | currentAlphabet = alphabetP;
1140 | else if (currentAlphabet == alphabetP)
1141 | currentAlphabet = alphabetU;
1142 | else
1143 | currentAlphabet = alphabetL;
1144 | if (version <= 2)
1145 | lockAlphabet = currentAlphabet;
1146 | break;
1147 | case 6 : // Literal output character if alphabet is P.
1148 | if (currentAlphabet == alphabetP) {
1149 | i++;
1150 | if (i >= zlen)
1151 | break;
1152 | c2 = (char)zchars[i];
1153 | i++;
1154 | if (i >= zlen)
1155 | break;
1156 | c3 = (char)zchars[i];
1157 | w = ((((int)c2 << 5) & 0x03e0) | ((int)c3 & 0x1f));
1158 | decodedstr.append(String.valueOf((char)w));
1159 | currentAlphabet = lockAlphabet;
1160 | }
1161 | else {
1162 | decodedstr.append(String.valueOf(alphabet[currentAlphabet][(int)c]));
1163 | currentAlphabet = lockAlphabet;
1164 | }
1165 | break;
1166 | default : decodedstr.append(String.valueOf(alphabet[currentAlphabet][(int)c]));
1167 | currentAlphabet = lockAlphabet;
1168 | break;
1169 | }
1170 | }
1171 |
1172 | // We're done!
1173 | return decodedstr.toString();
1174 | }
1175 |
1176 | // Encode text into a Z-String, represented as a vector of Integers.
1177 | private Vector encodeZString(String text)
1178 | {
1179 | Vector outbuf;
1180 | int curtextindex, textlen;
1181 | char curchar;
1182 | int i;
1183 | boolean found;
1184 |
1185 | outbuf = new Vector();
1186 | curtextindex = 0;
1187 | textlen = text.length();
1188 |
1189 | // Go through the string, converting characters as we go.
1190 | for (curtextindex=0;curtextindex> 5) & 0x1f))); // Top 5 bits
1261 | outbuf.addElement(new Integer(((int)curchar) & 0x1f)); // Bottom 5 bits
1262 | }
1263 |
1264 | // Return the encoded string.
1265 | return outbuf;
1266 | }
1267 |
1268 | // This function handles requests to get the value of a
1269 | // variable. Variable 0 refers to the top of the routine
1270 | // stack; variables 1-15 refer to local variables; and
1271 | // variables 16-255 refer to global variables.
1272 | private int getVariable(int v)
1273 | {
1274 | if (v == 0) { // The top of the routine stack
1275 | if (curCallFrame.routineStack.empty())
1276 | zui.fatal("Routine stack underflow");
1277 | else {
1278 | Integer i = (Integer)curCallFrame.routineStack.pop();
1279 | return (i.intValue());
1280 | }
1281 | }
1282 | else if ((v >= 1) && (v <= 15)) // Local variable
1283 | // We don't bother checking whether the variable
1284 | // exists -- the caller just gets a 0 if a non-existant
1285 | // local variable is referenced.
1286 | return (curCallFrame.localVars[v-1]);
1287 | else if ((v >= 16) && (v <= 255)) // Global variable
1288 | return (memory.fetchWord(globalVars + ((v - 16) * 2)));
1289 |
1290 | // If we get here, something's wrong.
1291 | zui.fatal("Unspecified variable referenced");
1292 | return(0); // To make javac happy
1293 | }
1294 |
1295 | // This function handles requests to put the value of a variable,
1296 | // as above.
1297 | private void putVariable(int v,int value)
1298 | {
1299 | value = value & 0xffff;
1300 | if (v == 0) { // Push this value onto the routine stack
1301 | Integer i = new Integer(value);
1302 | curCallFrame.routineStack.push(i);
1303 | }
1304 | else if ((v >= 1) && (v <= 15)) // Local variable
1305 | // Again, we don't bother checking the validity of
1306 | // the local variable number.
1307 | curCallFrame.localVars[v-1] = value;
1308 | else if ((v >= 16) && (v <= 255)) // Global variable
1309 | memory.putWord((globalVars + ((v - 16) * 2)),value);
1310 | else
1311 | zui.fatal("Unspecified variable referenced");
1312 | }
1313 |
1314 | // Unpack a packed address. raddr is true if this is a routine
1315 | // address.
1316 | private int unpackAddr(int paddr,boolean raddr)
1317 | {
1318 | int addr = 0;
1319 | int offset = 0;
1320 |
1321 | switch (version) {
1322 | case 1 :
1323 | case 2 :
1324 | case 3 : addr = 2 * paddr;
1325 | break;
1326 | case 4 :
1327 | case 5 : addr = 4 * paddr;
1328 | break;
1329 | case 6 :
1330 | case 7 : if (raddr)
1331 | offset = memory.fetchWord(0x28);
1332 | else
1333 | offset = memory.fetchWord(0x2a);
1334 | addr = (4 * addr) + (8 * offset);
1335 | break;
1336 | case 8 : addr = 8 * paddr;
1337 | break;
1338 | }
1339 |
1340 | return addr;
1341 | }
1342 |
1343 | // Return a signed version of a word
1344 | private int signedWord(int w)
1345 | {
1346 | if ((w & 0x8000) == 0x8000)
1347 | return (w - 65536);
1348 | else
1349 | return (w);
1350 | }
1351 |
1352 | // Encode a signed word
1353 | private int unsignedWord(int w)
1354 | {
1355 | w = w & 0xffff;
1356 | if (w < 0)
1357 | return (65536-(-w));
1358 | else
1359 | return (w);
1360 | }
1361 |
1362 | // Call the routine at the given routine address as an interrupt.
1363 | // Return the return value of the routine.
1364 | private int interrupt(int raddr)
1365 | {
1366 | int addr;
1367 | int newFrameAddr;
1368 | int numvars;
1369 |
1370 | // Unpack the routine address and get number of local variables
1371 | addr = unpackAddr(raddr,true);
1372 | numvars = memory.fetchByte(addr);
1373 | addr++;
1374 |
1375 | // Get a number for the new frame
1376 | newFrameAddr = curCallFrame.frameNumber + 1;
1377 |
1378 | // Push the current call frame onto the stack
1379 | callStack.push(curCallFrame);
1380 |
1381 | // Initialize a new call frame
1382 | curCallFrame = new ZCallFrame();
1383 |
1384 | // Set pc to the beginning of the routine's code
1385 | if (version < 5)
1386 | curCallFrame.pc = addr + (numvars * 2);
1387 | else
1388 | curCallFrame.pc = addr;
1389 |
1390 | // Get a new routine stack
1391 | curCallFrame.routineStack = new Stack();
1392 |
1393 | // Initialize local variables
1394 | for (int i=0;i
1571 | private void zop_print()
1572 | {
1573 | // The spec says to output the string as if a sequence
1574 | // of PRINT_CHAR instructions were executed. However,
1575 | // we output an entire string at a time for maximum
1576 | // drawing efficiency.
1577 | ioCard.printString(curString);
1578 | }
1579 |
1580 | // PRINT_RTRUE
1581 | private void zop_print_rtrue()
1582 | {
1583 | zop_print();
1584 | zop_new_line();
1585 | zop_rtrue();
1586 | }
1587 |
1588 | // NOP
1589 | private void zop_nop()
1590 | {
1591 | // No op.
1592 | return;
1593 | }
1594 |
1595 | // SAVE V1-3
1596 | // SAVE V4+
1597 | private void zop_save()
1598 | {
1599 | String fn;
1600 | FileOutputStream fos;
1601 | DataOutputStream dos;
1602 |
1603 | // Get a filename to save under
1604 | fn = zui.getFilename("Save Game",null,true);
1605 | if (fn == null) { // An error-probably user cancelled.
1606 | if (version <= 3)
1607 | dontBranch();
1608 | else
1609 | putVariable(curResult,0);
1610 | return;
1611 | }
1612 |
1613 | try {
1614 | fos = new FileOutputStream(fn);
1615 | dos = new DataOutputStream(fos);
1616 | dumpState(dos);
1617 | memory.dumpMemory(dos,0,dynamicMemorySize);
1618 | fos.close();
1619 | }
1620 | catch (IOException ex1) {
1621 | if (version <= 3)
1622 | dontBranch();
1623 | else
1624 | putVariable(curResult,0);
1625 | return;
1626 | }
1627 |
1628 | if (zui.getMonitor() != null) {
1629 | zui.getMonitor().doNotify();
1630 | }
1631 |
1632 | // We did it!
1633 | if (version <= 3)
1634 | doBranch();
1635 | else
1636 | putVariable(curResult,1);
1637 | }
1638 |
1639 | // RESTORE V1-3
1640 | // RESTORE V4
1641 | private void zop_restore()
1642 | {
1643 | String fn;
1644 | FileInputStream fis;
1645 | DataInputStream dis;
1646 | int tsBit;
1647 |
1648 | // Get a filename to restore from
1649 | fn = zui.getFilename("Restore Game",null,false);
1650 | if (fn == null) { // An error-probably user cancelled.
1651 | if (version >= 4)
1652 | putVariable(curResult,0);
1653 | return;
1654 | }
1655 |
1656 | // Remember the transcript bit
1657 | tsBit = memory.fetchWord(0x10) & 0x0001;
1658 |
1659 | try {
1660 | fis = new FileInputStream(fn);
1661 | dis = new DataInputStream(fis);
1662 | readState(dis);
1663 | memory.readMemory(dis,0,dynamicMemorySize);
1664 | fis.close();
1665 | }
1666 | catch (IOException ex1) {
1667 | if (version >= 4)
1668 | putVariable(curResult,0);
1669 | return;
1670 | }
1671 |
1672 | // We did it!
1673 | memory.putWord(0x10,memory.fetchWord(0x10) | tsBit);
1674 | if (version >= 3) {
1675 | curResult = memory.fetchByte(curCallFrame.pc - 1);
1676 | putVariable(curResult,2); // Is this correct?
1677 | }
1678 | }
1679 |
1680 | // RESTART
1681 | private void zop_restart()
1682 | {
1683 | // This will cause the decoder to exit and the ZMachine to restart
1684 | zui.restart();
1685 | restartFlag = true;
1686 | return;
1687 | }
1688 |
1689 | // RET_PULLED
1690 | private void zop_ret_pulled()
1691 | {
1692 | op1 = getVariable(0);
1693 | zop_ret();
1694 | }
1695 |
1696 | // POP
1697 | private void zop_pop()
1698 | {
1699 | getVariable(0);
1700 | }
1701 |
1702 | // CATCH V5+
1703 | private void zop_catch()
1704 | {
1705 | putVariable(curResult,curCallFrame.frameNumber);
1706 | }
1707 |
1708 | // QUIT
1709 | private void zop_quit()
1710 | {
1711 | zui.quit();
1712 | }
1713 |
1714 | // NEW_LINE
1715 | private void zop_new_line()
1716 | {
1717 | did_newline = true;
1718 | ioCard.printString("\n");
1719 | }
1720 |
1721 | // SHOW_STATUS V3
1722 | private void zop_show_status()
1723 | {
1724 | boolean timegame;
1725 | String s;
1726 | int a, b, name;
1727 |
1728 | // This instruction is known to appear spuriously in some
1729 | // V5 games (notably Wishbringer Solid Gold), so if this
1730 | // storyfile is not V1-3, we'll ignore it.
1731 | if (version > 3)
1732 | return;
1733 |
1734 | // Find out if this is a time game or not. Can this change
1735 | // during a game? I'm assuming it can.
1736 | if ((memory.fetchByte(0x01) & 0x02) == 0x02)
1737 | timegame = true;
1738 | else
1739 | timegame = false;
1740 |
1741 | // Get the current location name
1742 | name = objTable.getObjectName(getVariable(16));
1743 | s = decodeZString(name);
1744 |
1745 | // Get the two integers
1746 | a = signedWord(getVariable(17));
1747 | b = signedWord(getVariable(18));
1748 |
1749 | // Pass it on to the user interface.
1750 | zui.showStatusBar(s,a,b,timegame);
1751 | }
1752 |
1753 | // VERIFY
1754 | private void zop_verify()
1755 | {
1756 | // VERIFY is always successful for now. I've had problems getting it working,
1757 | // and it's not a high priority.
1758 | doBranch();
1759 | }
1760 |
1761 | // PIRACY V5+
1762 | private void zop_piracy()
1763 | {
1764 | // This always branches.
1765 | doBranch();
1766 | }
1767 |
1768 |
1769 | ///////////////////////////////////////////////////////////////////
1770 | // 1OPs
1771 | ///////////////////////////////////////////////////////////////////
1772 |
1773 | // JZ a
1774 | private void zop_jz()
1775 | {
1776 | if (op1 == 0)
1777 | doBranch();
1778 | else
1779 | dontBranch();
1780 | }
1781 |
1782 | // GET_SIBLING obj
1783 | private void zop_get_sibling()
1784 | {
1785 | int sib;
1786 |
1787 | sib = objTable.getSibling(op1);
1788 | putVariable(curResult,sib);
1789 | if (sib != 0)
1790 | doBranch();
1791 | else
1792 | dontBranch();
1793 | }
1794 |
1795 | // GET_CHILD obj
1796 | private void zop_get_child()
1797 | {
1798 | int child;
1799 |
1800 | child = objTable.getChild(op1);
1801 | putVariable(curResult,child);
1802 | if (child != 0)
1803 | doBranch();
1804 | else
1805 | dontBranch();
1806 | }
1807 |
1808 | // GET_PARENT obj
1809 | private void zop_get_parent()
1810 | {
1811 | int parent;
1812 |
1813 | parent = objTable.getParent(op1);
1814 | putVariable(curResult,parent);
1815 | }
1816 |
1817 | // GET_PROP_LEN baddr
1818 | private void zop_get_prop_len()
1819 | {
1820 | int len;
1821 |
1822 | len = objTable.getPropertyLength(op1);
1823 | putVariable(curResult,len);
1824 | }
1825 |
1826 | // INC var
1827 | private void zop_inc()
1828 | {
1829 | int w;
1830 |
1831 | w = signedWord(getVariable(op1));
1832 | w = ((w + 1) % 0x10000);
1833 | putVariable(op1,w);
1834 | }
1835 |
1836 | // DEC var
1837 | private void zop_dec()
1838 | {
1839 | int w;
1840 |
1841 | w = signedWord(getVariable(op1));
1842 | w = ((w - 1) % 0x10000);
1843 | putVariable(op1,w);
1844 | }
1845 |
1846 | // PRINT_ADDR addr
1847 | private void zop_print_addr()
1848 | {
1849 | String s;
1850 |
1851 | s = decodeZString(op1);
1852 | ioCard.printString(s);
1853 | }
1854 |
1855 | // CALL_F0 raddr V4+
1856 | private void zop_call_f0()
1857 | {
1858 | numvops = 1;
1859 | vops[0] = op1;
1860 | zop_call_fv();
1861 | }
1862 |
1863 | // REMOVE_OBJ obj
1864 | private void zop_remove_obj()
1865 | {
1866 | int parent;
1867 |
1868 | parent = objTable.getParent(op1);
1869 | if (op1 == 0)
1870 | return; // No parent, no service.
1871 | objTable.removeObject(parent,op1);
1872 | }
1873 |
1874 | // PRINT_OBJ obj
1875 | private void zop_print_obj()
1876 | {
1877 | int addr;
1878 | String s;
1879 |
1880 | addr = objTable.getObjectName(op1);
1881 | s = decodeZString(addr);
1882 | ioCard.printString(s);
1883 | }
1884 |
1885 | // RET a
1886 | private void zop_ret()
1887 | {
1888 | // First, make sure we *can* return.
1889 | if (callStack.empty())
1890 | zui.fatal("Call stack underflow");
1891 |
1892 | // Now do the appropriate thing for each call type.
1893 | if (curCallFrame.callType == ZCallFrame.PROCEDURE) {
1894 | curCallFrame = (ZCallFrame)callStack.pop();
1895 | return;
1896 | }
1897 | else if (curCallFrame.callType == ZCallFrame.FUNCTION) {
1898 | curCallFrame = (ZCallFrame)callStack.pop();
1899 | curResult = memory.fetchByte(curCallFrame.pc);
1900 | curCallFrame.pc++;
1901 | putVariable(curResult,op1);
1902 | return;
1903 | }
1904 | else if (curCallFrame.callType == ZCallFrame.INTERRUPT) {
1905 | curCallFrame = (ZCallFrame)callStack.pop();
1906 | decode_ret_flag = true;
1907 | ret_value = op1;
1908 | return;
1909 | }
1910 |
1911 | // If we make it here, something is wrong.
1912 | zui.fatal("Corrupted call frame");
1913 | return;
1914 | }
1915 |
1916 | // JUMP s
1917 | private void zop_jump()
1918 | {
1919 | int sop1;
1920 |
1921 | sop1 = signedWord(op1);
1922 |
1923 | curCallFrame.pc = curCallFrame.pc + sop1 - 2;
1924 | }
1925 |
1926 | // PRINT_PADDR saddr
1927 | private void zop_print_paddr()
1928 | {
1929 | int addr;
1930 | String s;
1931 |
1932 | addr = unpackAddr(op1,false);
1933 | s = decodeZString(addr);
1934 | ioCard.printString(s);
1935 | }
1936 |
1937 | // LOAD var
1938 | private void zop_load()
1939 | {
1940 | int w;
1941 |
1942 | w = getVariable(op1);
1943 | putVariable(curResult,w);
1944 | }
1945 |
1946 | // NOT a
1947 | private void zop_not()
1948 | {
1949 | int val;
1950 |
1951 | if (op1type == ARGTYPE_WORD)
1952 | val = ((~op1) & 0xffff);
1953 | else
1954 | val = ((~op1) & 0xff);
1955 | putVariable(curResult,val);
1956 | }
1957 |
1958 | // CALL_P0 raddr V5+
1959 | private void zop_call_p0()
1960 | {
1961 | numvops = 1;
1962 | vops[0] = op1;
1963 | zop_call_pv();
1964 | }
1965 |
1966 |
1967 | ///////////////////////////////////////////////////////////////////
1968 | // 2OPs
1969 | ///////////////////////////////////////////////////////////////////
1970 |
1971 | // JE a [b1 b2 b3]
1972 | private void zop_je()
1973 | {
1974 | if (curInstruction == 0xc1) { // The variable version
1975 | for (int i = 1; i
1995 | private void zop_jl()
1996 | {
1997 | int sop1, sop2;
1998 |
1999 | sop1 = signedWord(op1);
2000 |
2001 | sop2 = signedWord(op2);
2002 |
2003 | if (sop1 < sop2)
2004 | doBranch();
2005 | else
2006 | dontBranch();
2007 | }
2008 |
2009 | // JG s t
2010 | private void zop_jg()
2011 | {
2012 | int sop1, sop2;
2013 |
2014 | sop1 = signedWord(op1);
2015 |
2016 | sop2 = signedWord(op2);
2017 |
2018 | if (sop1 > sop2)
2019 | doBranch();
2020 | else
2021 | dontBranch();
2022 | }
2023 |
2024 | // DEC_JL var s
2025 | private void zop_dec_jl()
2026 | {
2027 | // DEC var
2028 | zop_dec();
2029 |
2030 | // JL var s
2031 | op1 = getVariable(op1);
2032 | op1type = ARGTYPE_WORD;
2033 | zop_jl();
2034 | }
2035 |
2036 | // INC_JG var t ()
2037 | private void zop_inc_jg()
2038 | {
2039 | // INC var
2040 | zop_inc();
2041 |
2042 | // JG var t
2043 | op1 = getVariable(op1);
2044 | op1type = ARGTYPE_WORD;
2045 | zop_jg();
2046 | }
2047 |
2048 | // JIN obj n
2049 | private void zop_jin()
2050 | {
2051 | int parent;
2052 |
2053 | parent = objTable.getParent(op1);
2054 |
2055 | if (parent == op2)
2056 | doBranch();
2057 | else
2058 | dontBranch();
2059 | }
2060 |
2061 | // TEST a b
2062 | private void zop_test()
2063 | {
2064 | if ((op1 & op2) == op2)
2065 | doBranch();
2066 | else
2067 | dontBranch();
2068 | }
2069 |
2070 | // OR a b
2071 | private void zop_or()
2072 | {
2073 | putVariable(curResult,(op1 | op2));
2074 | }
2075 |
2076 | // AND a b
2077 | private void zop_and()
2078 | {
2079 | putVariable(curResult,(op1 & op2));
2080 | }
2081 |
2082 | // TEST_ATTR obj attr
2083 | private void zop_test_attr()
2084 | {
2085 | if (objTable.hasAttribute(op1,op2))
2086 | doBranch();
2087 | else
2088 | dontBranch();
2089 | }
2090 |
2091 | // SET_ATTR obj attr
2092 | private void zop_set_attr()
2093 | {
2094 | objTable.setAttribute(op1,op2);
2095 | }
2096 |
2097 | // CLEAR_ATTR obj attr
2098 | private void zop_clear_attr()
2099 | {
2100 | objTable.clearAttribute(op1,op2);
2101 | }
2102 |
2103 | // STORE var a
2104 | private void zop_store()
2105 | {
2106 | putVariable(op1,op2);
2107 | }
2108 |
2109 | // INSERT_OBJ obj1 obj2
2110 | private void zop_insert_obj()
2111 | {
2112 | objTable.insertObject(op1,op2);
2113 | }
2114 |
2115 | // LOADW baddr n
2116 | private void zop_loadw()
2117 | {
2118 | putVariable(curResult,memory.fetchWord(op1 + (2 * op2)));
2119 | }
2120 |
2121 | // LOADB baddr n
2122 | private void zop_loadb()
2123 | {
2124 | putVariable(curResult,memory.fetchByte(op1+op2));
2125 | }
2126 |
2127 | // GET_PROP obj prop
2128 | private void zop_get_prop()
2129 | {
2130 | int prop;
2131 |
2132 | prop = objTable.getProperty(op1,op2);
2133 | putVariable(curResult,prop);
2134 | }
2135 |
2136 | // GET_PROP_ADDR obj prop
2137 | private void zop_get_prop_addr()
2138 | {
2139 | int addr;
2140 |
2141 | addr = objTable.getPropertyAddress(op1,op2);
2142 | putVariable(curResult,addr);
2143 | }
2144 |
2145 | // GET_NEXT_PROP obj prop
2146 | private void zop_get_next_prop()
2147 | {
2148 | int num;
2149 |
2150 | num = objTable.getNextProperty(op1,op2);
2151 | putVariable(curResult,num);
2152 | }
2153 |
2154 | // ADD a b
2155 | private void zop_add()
2156 | {
2157 | int sop1, sop2;
2158 |
2159 | sop1 = signedWord(op1);
2160 |
2161 | sop2 = signedWord(op2);
2162 |
2163 | putVariable(curResult,unsignedWord(sop1 + sop2));
2164 | }
2165 |
2166 | // SUB a b
2167 | private void zop_sub()
2168 | {
2169 | int sop1, sop2;
2170 |
2171 | sop1 = signedWord(op1);
2172 |
2173 | sop2 = signedWord(op2);
2174 |
2175 | putVariable(curResult,unsignedWord(sop1 - sop2));
2176 | }
2177 |
2178 | // MUL a b
2179 | private void zop_mul()
2180 | {
2181 | int sop1, sop2;
2182 |
2183 | sop1 = signedWord(op1);
2184 |
2185 | sop2 = signedWord(op2);
2186 |
2187 | putVariable(curResult,unsignedWord(sop1 * sop2));
2188 | }
2189 |
2190 | // DIV a b
2191 | private void zop_div()
2192 | {
2193 | int sop1, sop2;
2194 |
2195 | if (op2 == 0)
2196 | zui.fatal("Divide by zero");
2197 |
2198 | sop1 = signedWord(op1);
2199 |
2200 | sop2 = signedWord(op2);
2201 |
2202 | putVariable(curResult,unsignedWord(sop1 / sop2));
2203 | }
2204 |
2205 | // MOD a b
2206 | private void zop_mod()
2207 | {
2208 | int sop1, sop2;
2209 |
2210 | if (op2 == 0) {
2211 | putVariable(curResult,op1);
2212 | return;
2213 | }
2214 |
2215 | sop1 = signedWord(op1);
2216 |
2217 | sop2 = signedWord(op2);
2218 |
2219 | putVariable(curResult,unsignedWord(sop1 % sop2));
2220 | }
2221 |
2222 | // CALL_F1 raddr a1 V4+
2223 | private void zop_call_f1()
2224 | {
2225 | numvops = 2;
2226 | vops[0] = op1;
2227 | vops[1] = op2;
2228 | zop_call_fv();
2229 | }
2230 |
2231 | // CALL_P1 raddr a1 V5+
2232 | private void zop_call_p1()
2233 | {
2234 | numvops = 2;
2235 | vops[0] = op1;
2236 | vops[1] = op2;
2237 | zop_call_pv();
2238 | }
2239 |
2240 | // SET_COLOUR f b V5+
2241 | private void zop_set_colour()
2242 | {
2243 | if (op1 == 1)
2244 | op1 = memory.fetchByte(0x2d);
2245 | if (op2 == 1)
2246 | op2 = memory.fetchByte(0x2c);
2247 | zui.setColor(op1,op2);
2248 | }
2249 |
2250 | // THROW a fp V5+
2251 | private void zop_throw()
2252 | {
2253 | // Pop the stack until we either find the frame being referenced, or the
2254 | // stack underflows (a fatal error).
2255 | while ((curCallFrame.frameNumber != op2) && (!callStack.empty()))
2256 | curCallFrame = (ZCallFrame)callStack.pop();
2257 | if (curCallFrame.frameNumber != op2) // Stack underflow
2258 | zui.fatal("THROW: Call stack underflow");
2259 |
2260 | // We have the frame; now do a RET a
2261 | zop_ret();
2262 | }
2263 |
2264 |
2265 | ///////////////////////////////////////////////////////////////////
2266 | // VARs
2267 | ///////////////////////////////////////////////////////////////////
2268 |
2269 | // CALL_FV raddr [a1 a2 a3]
2270 | // CALL_FD raddr [a1 a2 a3 a4 a5 a6 a7] V4+
2271 | private void zop_call_fv()
2272 | {
2273 | int addr;
2274 | int numvars;
2275 | int numargs;
2276 | int newFrameNumber;
2277 |
2278 | // First, make sure raddr is not 0
2279 | if (vops[0] == 0) {
2280 | putVariable(curResult,0);
2281 | return;
2282 | }
2283 |
2284 | // Get the number of arguments
2285 | numargs = numvops - 1;
2286 |
2287 | // Unpack the routine address
2288 | addr = unpackAddr(vops[0],true);
2289 |
2290 | // System.out.println(Integer.toHexString(curCallFrame.pc) + " CALL " + Integer.toHexString(addr) + " " + vops[1] + " " + vops[2] + " " + vops[3] + " " + vops[4] + " " + vops[5] + " " + vops[6] + " " + vops[7] + "(" + numvops + ")" + " " + Integer.toHexString(getVariable(3)) + " " + Integer.toHexString(getVariable(5)));
2291 | // Get the number of local variables
2292 | numvars = memory.fetchByte(addr);
2293 |
2294 | // Bump the address past the variables byte, in any version
2295 | addr++;
2296 |
2297 | // Back up the PC to point to the result byte
2298 | curCallFrame.pc--;
2299 |
2300 | // Get the number of the next call frame.
2301 | newFrameNumber = curCallFrame.frameNumber + 1;
2302 |
2303 | // Push the current call frame onto the stack.
2304 | callStack.push(curCallFrame);
2305 |
2306 | // Initialize a new call frame
2307 | curCallFrame = new ZCallFrame();
2308 |
2309 | // Put the PC at the appropriate place, depending on
2310 | // whether local variables are present.
2311 | if (version < 5)
2312 | curCallFrame.pc = addr + (numvars * 2);
2313 | else
2314 | curCallFrame.pc = addr;
2315 |
2316 | // Create an empty routine stack
2317 | curCallFrame.routineStack = new Stack();
2318 |
2319 | // Initialize local variables
2320 | curCallFrame.numLocalVars = numvars;
2321 | for (int i = 0;i < numvars;i++) {
2322 | // Fill in an argument in this variable, if one exists.
2323 | if (i < numargs) {
2324 | curCallFrame.localVars[i] = vops[i + 1];
2325 | continue;
2326 | }
2327 |
2328 | // Otherwise, if this is a pre-V5 game, fill in
2329 | // a local variable.
2330 | if (version < 5) {
2331 | curCallFrame.localVars[i] = memory.fetchWord(addr + (i * 2));
2332 | continue;
2333 | }
2334 |
2335 | // Otherwise, just make this variable 0.
2336 | curCallFrame.localVars[i] = 0;
2337 | }
2338 |
2339 | // Store the call type (only strictly necessary in V3+)
2340 | curCallFrame.callType = ZCallFrame.FUNCTION;
2341 |
2342 | // Store the number of arguments (only strictly necessary
2343 | // in V5+)
2344 | if (numargs > numvars)
2345 | curCallFrame.argCount = numvars;
2346 | else
2347 | curCallFrame.argCount = numargs;
2348 |
2349 | // Stor the call frame number
2350 | curCallFrame.frameNumber = newFrameNumber;
2351 | }
2352 |
2353 | // STOREW baddr n a
2354 | private void zop_storew()
2355 | {
2356 | memory.putWord((vops[0] + (2 * vops[1])),vops[2]);
2357 | }
2358 |
2359 | // STOREB baddr n byte
2360 | private void zop_storeb()
2361 | {
2362 | memory.putByte((vops[0] + vops[1]),vops[2]);
2363 | }
2364 |
2365 | // PUT_PROP obj prop a
2366 | private void zop_put_prop()
2367 | {
2368 | objTable.putProperty(vops[0],vops[1],vops[2]);
2369 | }
2370 |
2371 | // READ baddr1 baddr2 V1-3
2372 | // READ baddr1 baddr2 [time raddr] V4
2373 | // READ baddr1 baddr2 [time raddr] V5+
2374 | private void zop_read()
2375 | {
2376 | String s;
2377 | StringBuffer sb;
2378 | int termChar;
2379 | int len;
2380 | int curaddr;
2381 | int baddr1, baddr2;
2382 | int time = 0, raddr = 0;
2383 |
2384 | baddr1 = vops[0];
2385 | baddr2 = vops[1];
2386 | if (numvops > 2) {
2387 | time = vops[2];
2388 | raddr = vops[3];
2389 | }
2390 |
2391 | // Flush the I/O card's output buffer
2392 | ioCard.outputFlush();
2393 |
2394 | // This implies a SHOW_STATUS in V1-3.
2395 | if (version < 4)
2396 | zop_show_status();
2397 |
2398 | // Read a line of text
2399 | sb = new StringBuffer();
2400 | if ((time > 0) && (raddr > 0)) { // A timed READ
2401 | while (true) { // Ick.
2402 | termChar = ioCard.readLine(sb,time);
2403 | if (termChar == -1) { // A timeout
2404 | // ioCard.outputFlush();
2405 | // did_newline = false;
2406 | for (int i = 0; i < sb.length(); i++)
2407 | ioCard.printString("\b");
2408 | int rc = interrupt(raddr);
2409 | if (rc == 0) {
2410 | // if (did_newline) {
2411 | // ioCard.printString("\n" + sb.toString());
2412 | // ioCard.outputFlush();
2413 | // }
2414 | ioCard.printString(sb.toString());
2415 | ioCard.outputFlush();
2416 | continue;
2417 | }
2418 | else {
2419 | ioCard.outputFlush();
2420 | sb = new StringBuffer();
2421 | termChar = 0;
2422 | break;
2423 | }
2424 | }
2425 | else // Not a timeout
2426 | break;
2427 | }
2428 | }
2429 | else
2430 | termChar = ioCard.readLine(sb,0);
2431 | s = sb.toString();
2432 |
2433 | // If V1-4, just store the line. If V5+, possibly
2434 | // store it after other characters in the buffer.
2435 | if (version <= 4) {
2436 | curaddr = baddr1 + 1;
2437 | len = s.length();
2438 | for (int i = 0;i < len;i++) {
2439 | memory.putByte(curaddr,Character.toLowerCase(s.charAt(i)));
2440 | curaddr++;
2441 | }
2442 | memory.putByte(curaddr,0);
2443 | }
2444 | else {
2445 | int nchars = memory.fetchByte(baddr1 + 1);
2446 | curaddr = baddr1 + 2 + nchars;
2447 | len = s.length();
2448 | for (int i = 0;i < len;i++) {
2449 | memory.putByte(curaddr,Character.toLowerCase(s.charAt(i)));
2450 | curaddr++;
2451 | }
2452 | memory.putByte(baddr1+1,(nchars + len));
2453 | }
2454 |
2455 | // Tokenize input
2456 | if (baddr2 != 0) {
2457 | vops[0] = baddr1;
2458 | vops[1] = baddr2;
2459 | numvops = 2;
2460 | zop_tokenise();
2461 | }
2462 |
2463 | // If V5+, store result
2464 | if (version >= 5)
2465 | putVariable(curResult,termChar);
2466 | }
2467 |
2468 | // PRINT_CHAR n
2469 | private void zop_print_char()
2470 | {
2471 | String s;
2472 |
2473 | s = new String(String.valueOf((char)vops[0]));
2474 | ioCard.printString(s);
2475 | }
2476 |
2477 | // PRINT_NUM s
2478 | private void zop_print_num()
2479 | {
2480 | int sop1;
2481 | String s;
2482 |
2483 | sop1 = signedWord(vops[0]);
2484 |
2485 | s = new String(String.valueOf(sop1));
2486 | ioCard.printString(s);
2487 | }
2488 |
2489 | // RANDOM s
2490 | private void zop_random()
2491 | {
2492 | if (signedWord(vops[0]) > 0)
2493 | putVariable(curResult,rndgen.getRandom(signedWord(vops[0])));
2494 | else {
2495 | rndgen.seed(signedWord(vops[0]));
2496 | putVariable(curResult,0);
2497 | }
2498 | }
2499 |
2500 | // PUSH a
2501 | private void zop_push()
2502 | {
2503 | putVariable(0,vops[0]);
2504 | }
2505 |
2506 | // PULL var V1-5,7-8
2507 | // PULL [baddr] V6
2508 | private void zop_pull()
2509 | {
2510 | // This will need to be extended for V6 support
2511 | putVariable(vops[0],getVariable(0));
2512 | }
2513 |
2514 | // SPLIT_SCREEN n V3+
2515 | private void zop_split_screen()
2516 | {
2517 | ioCard.outputFlush();
2518 | zui.splitScreen(vops[0]);
2519 | }
2520 |
2521 | // SET_WINDOW window V3+
2522 | private void zop_set_window()
2523 | {
2524 | ioCard.outputFlush();
2525 |
2526 | // In V6, -3 represents the current window
2527 | zui.setCurrentWindow(vops[0]);
2528 | }
2529 |
2530 | // CALL_FD raddr [a1 a2 a3 a4 a5 a6 a7] V4+
2531 | private void zop_call_fd()
2532 | {
2533 | // CALL_FV actually handles this
2534 | zop_call_fv();
2535 | }
2536 |
2537 | // ERASE_WINDOW window V4+
2538 | private void zop_erase_window()
2539 | {
2540 | int sop1;
2541 |
2542 | sop1 = signedWord(vops[0]);
2543 | if (sop1 == -1) { // Erase everything, do a SPLIT_SCREEN 0
2544 | zui.eraseWindow(0);
2545 | zui.eraseWindow(1);
2546 | vops[0] = 0;
2547 | numvops = 1;
2548 | zop_split_screen();
2549 | return;
2550 | }
2551 | else // In V6, we'll have to handle -2 explicitly
2552 | zui.eraseWindow(vops[0]);
2553 | }
2554 |
2555 | // ERASE_LINE V4-5,7-8
2556 | // ERASE_LINE n V6
2557 | private void zop_erase_line()
2558 | {
2559 | zui.eraseLine(1);
2560 | }
2561 |
2562 | // SET_CURSOR s x V4-5,7-8
2563 | // SET_CURSOR s x [window] V6
2564 | private void zop_set_cursor()
2565 | {
2566 | ioCard.outputFlush();
2567 | zui.setCursorPosition(vops[1],vops[0]);
2568 | }
2569 |
2570 | // GET_CURSOR baddr V4+
2571 | private void zop_get_cursor()
2572 | {
2573 | Point p;
2574 |
2575 | ioCard.outputFlush();
2576 | p = zui.getCursorPosition();
2577 | memory.putWord(vops[0],p.y);
2578 | memory.putWord(vops[0]+2,p.x);
2579 | }
2580 |
2581 | // SET_TEXT_STYLE n V4+
2582 | private void zop_set_text_style()
2583 | {
2584 | ioCard.outputFlush();
2585 | zui.setTextStyle(vops[0]);
2586 | Dimension s = zui.getFontSize();
2587 | memory.putByte(0x26,s.height);
2588 | memory.putByte(0x27,s.width);
2589 | }
2590 |
2591 | // BUFFER_MODE bit V4+
2592 | private void zop_buffer_mode()
2593 | {
2594 | // This doesn't really fit in with our buffering method, so we ignore it.
2595 | // Flush the buffer, though.
2596 | ioCard.outputFlush();
2597 | }
2598 |
2599 | // OUTPUT_STREAM s V3-4
2600 | // OUTPUT_STREAM s [baddr] V5,7-8
2601 | // OUTPUT_STREAM s [baddr w] V6
2602 | private void zop_output_stream()
2603 | {
2604 | int w;
2605 |
2606 | if (numvops == 3)
2607 | ioCard.setOutputStream(signedWord(vops[0]),vops[1],vops[2],true);
2608 | else
2609 | ioCard.setOutputStream(signedWord(vops[0]),vops[1],0,false);
2610 | }
2611 |
2612 | // INPUT_STREAM n V3+
2613 | private void zop_input_stream()
2614 | {
2615 | ioCard.setInputStream(vops[0]);
2616 | }
2617 |
2618 | // SOUND n [op time raddr] V3+
2619 | private void zop_sound()
2620 | {
2621 | // Silently fail on this instruction if no raddr argument;
2622 | // otherwise, go straight to raddr for now.
2623 | if (numvops == 1)
2624 | return;
2625 |
2626 | if (vops[1] != 2)
2627 | return;
2628 |
2629 | // Pretend a CALL_P0 has just been executed.
2630 | op1 = vops[3];
2631 | op1type = voptypes[3];
2632 | zop_call_p0();
2633 | }
2634 |
2635 | // READ_CHAR 1 [time raddr] V4+
2636 | private void zop_read_char()
2637 | {
2638 | int c;
2639 |
2640 | ioCard.outputFlush();
2641 | if ((numvops > 1) && (vops[1] != 0) && (vops[2] != 0)) { // A timed READ_CHAR
2642 | while (true) { // Yuck.
2643 | c = ioCard.readChar(vops[1]);
2644 | if (c == -1) { // A timeout
2645 | int rc = interrupt(vops[2]);
2646 | if (rc == 0)
2647 | continue;
2648 | else {
2649 | putVariable(curResult,0);
2650 | return;
2651 | }
2652 | }
2653 | else { // A character
2654 | putVariable(curResult,c);
2655 | return;
2656 | }
2657 | }
2658 | }
2659 | else {
2660 | c = ioCard.readChar(0);
2661 | putVariable(curResult,c);
2662 | }
2663 | }
2664 |
2665 | // SCAN_TABLE a baddr n [byte] V4+
2666 | private void zop_scan_table()
2667 | {
2668 | int a, baddr, n, format;
2669 | boolean searchWord; // Searching for a word (not a byte)?
2670 | int tableWidth; // Width of table
2671 | int testAddr; // Address to test
2672 | int testData; // Data to test
2673 |
2674 | // Get operands
2675 | a = vops[0];
2676 | baddr = vops[1];
2677 | n = vops[2];
2678 | if (numvops == 4)
2679 | format = vops[3];
2680 | else
2681 | format = 0x82;
2682 | if ((format & 0x80) == 0x80)
2683 | searchWord = true;
2684 | else
2685 | searchWord = false;
2686 | tableWidth = (format & 0x7f);
2687 |
2688 | // Fail if it's a table of bytes and a is word-valued
2689 | if ((tableWidth == 2) && (voptypes[0] == ARGTYPE_BYTE)) {
2690 | putVariable(curResult,0);
2691 | dontBranch();
2692 | return;
2693 | }
2694 |
2695 | // Search the table
2696 | for (int i=0;i numvars)
2789 | curCallFrame.argCount = numvars;
2790 | else
2791 | curCallFrame.argCount = numargs;
2792 |
2793 | // Store the call frame number
2794 | curCallFrame.frameNumber = newFrameNumber;
2795 | }
2796 |
2797 | // TOKENISE baddr1 baddr2 [baddr3 bit] V5+
2798 | // This code could definitely be improved.
2799 | private void zop_tokenise()
2800 | {
2801 | int dictaddr, numseparators, dictentrysize, numdictentries, bit;
2802 | int maxtokens, numtokens, maxtokenlen;
2803 | String s, delimiters, thistoken;
2804 | int c, len, curaddr, strpos;
2805 | StringTokenizer tokens;
2806 | Vector encodedtoken;
2807 | int i, j, k, curword, curzchar, curindex;
2808 | boolean match = true;
2809 | Integer n;
2810 |
2811 | if (numvops > 2) {
2812 | dictaddr = vops[2];
2813 | bit = vops[3];
2814 | }
2815 | else {
2816 | dictaddr = mainDictionary;
2817 | bit = 0;
2818 | }
2819 |
2820 | // Get the maximum number of tokens
2821 | maxtokens = memory.fetchByte(vops[1]);
2822 |
2823 | // Set maximum token length, in words
2824 | if (version < 4)
2825 | maxtokenlen = 2;
2826 | else
2827 | maxtokenlen = 3;
2828 |
2829 | // Construct a Java string from the input string.
2830 | s = new String();
2831 | if (version <= 4) { // Null-terminated input string
2832 | curaddr = vops[0] + 1;
2833 | c = memory.fetchByte(curaddr);
2834 | while (c != 0) {
2835 | s = s + String.valueOf((char)c);
2836 | curaddr++;
2837 | c = memory.fetchByte(curaddr);
2838 | }
2839 | }
2840 | else { // String with length value
2841 | len = memory.fetchByte(vops[0] + 1);
2842 | curaddr = vops[0] + 2;
2843 | for (i=0;i=0;k--) {
2888 | curzchar = ((curword >> (k*5)) & 0x1f);
2889 | if (curindex == encodedtoken.size()) {
2890 | if (curzchar != 5) {
2891 | match = false;
2892 | break;
2893 | }
2894 | else
2895 | continue;
2896 | }
2897 | else { // curindex valid
2898 | n = (Integer)encodedtoken.elementAt(curindex);
2899 | curindex++;
2900 | if (curzchar != n.intValue()) {
2901 | match = false;
2902 | break;
2903 | }
2904 | }
2905 | }
2906 |
2907 | // Break out of this token comparison if we know it's false.
2908 | if (!match)
2909 | break;
2910 | }
2911 |
2912 | // Break out of the dictionary walk if this token matches.
2913 | if (match)
2914 | break;
2915 | }
2916 |
2917 | // This is a kludge. It is possible for match to be true if
2918 | // the loop exits at the end of the dictionary.
2919 | if (i >= numdictentries)
2920 | match = false;
2921 |
2922 | // Store the token. i still is the dictionary entry number of the matched entry.
2923 | // Sneaky--in v1-4, there is 1 length byte at the front of the input buffer. In v5+,
2924 | // there are 2. See zop_read.
2925 | int bufferOffset;
2926 | if (version <= 4) {
2927 | bufferOffset = 1;
2928 | } else {
2929 | bufferOffset = 2;
2930 | }
2931 | if (match) {
2932 | memory.putWord((vops[1] + 2 + (numtokens * 4)),(curaddr + (i * dictentrysize))); // Memory location of dictionary entry
2933 | memory.putByte((vops[1] + 2 + (numtokens * 4) + 2),thistoken.length()); // Length of word
2934 | memory.putByte((vops[1] + 2 + (numtokens * 4) + 3),(strpos + bufferOffset)); // Position in input buffer; see above
2935 | }
2936 | else if (bit == 0) { // If bit is set, leave the slot alone
2937 | memory.putWord((vops[1] + 2 + (numtokens * 4)),0);
2938 | memory.putByte((vops[1] + 2 + (numtokens * 4) + 2),thistoken.length()); // Length of word
2939 | memory.putByte((vops[1] + 2 + (numtokens * 4) + 3),(strpos + bufferOffset)); // Position in input buffer; see above
2940 | }
2941 |
2942 |
2943 | strpos += thistoken.length();
2944 | numtokens++;
2945 | }
2946 |
2947 | // Finally, store the number of tokens tokenized in the parse buffer.
2948 | memory.putByte((vops[1] + 1),numtokens);
2949 | }
2950 |
2951 | // ENCODE_TEXT baddr1 p n baddr2 V5+
2952 | private void zop_encode_text()
2953 | {
2954 | String s;
2955 | Vector encodedstr;
2956 | int curindex, maxlen, encodedlen;
2957 | int i, w;
2958 | Integer n;
2959 |
2960 | // First, make a string out of the text to encode.
2961 | s = new String();
2962 | for (i=0;i=0;i--)
3027 | memory.putByte(vops[1]+i,memory.fetchByte(vops[0]+i));
3028 | return;
3029 | }
3030 |
3031 | // PRINT_TABLE baddr x [y n] V5+
3032 | private void zop_print_table()
3033 | {
3034 | int baddr, x, y, n;
3035 | int baseX, curY, lineAddr;
3036 | int c;
3037 |
3038 | // Get operands
3039 | baddr = vops[0];
3040 | x = vops[1];
3041 | if (numvops == 4) {
3042 | y = vops[2];
3043 | n = vops[3];
3044 | }
3045 | else {
3046 | y = 1;
3047 | n = 0;
3048 | }
3049 |
3050 | // If y == 0, forget it (recommended by Klooster)
3051 | if (y == 0)
3052 | return;
3053 |
3054 | // Print the table
3055 | Point p = zui.getCursorPosition();
3056 | baseX = p.x;
3057 | curY = p.y;
3058 | lineAddr = baddr;
3059 | for (int i=0;i V5+
3072 | private void zop_check_arg_count()
3073 | {
3074 | if (curCallFrame.argCount >= vops[0])
3075 | doBranch();
3076 | else
3077 | dontBranch();
3078 | }
3079 |
3080 |
3081 | ///////////////////////////////////////////////////////////////////
3082 | // EXTs
3083 | ///////////////////////////////////////////////////////////////////
3084 |
3085 | // SAVE [baddr1 n baddr2] V5+
3086 | private void zop_ext_save()
3087 | {
3088 | String fn;
3089 | String suggested;
3090 | FileOutputStream fos;
3091 | DataOutputStream dos;
3092 | int slen;
3093 |
3094 | // If there are no arguments, do a normal save
3095 | if (numvops == 0) {
3096 | zop_save();
3097 | return;
3098 | }
3099 |
3100 | // Get a filename to save under
3101 | suggested = null;
3102 | if ((numvops > 2) && (vops[2] != 0)) {
3103 | slen = memory.fetchByte(vops[2]);
3104 | if (slen > 0) {
3105 | StringBuffer tmp = new StringBuffer();
3106 | for (int i = 1;i <= slen;i++)
3107 | tmp.append(String.valueOf((char)memory.fetchByte(vops[2]+i)));
3108 | suggested = tmp.toString();
3109 | }
3110 | }
3111 | fn = zui.getFilename("Save Auxiliary File",suggested,true);
3112 | if (fn == null) { // An error-probably user cancelled.
3113 | putVariable(curResult,0);
3114 | return;
3115 | }
3116 |
3117 | try {
3118 | fos = new FileOutputStream(fn);
3119 | dos = new DataOutputStream(fos);
3120 | memory.dumpMemory(dos,vops[0],vops[1]);
3121 | fos.close();
3122 | }
3123 | catch (IOException ex1) {
3124 | putVariable(curResult,0);
3125 | return;
3126 | }
3127 |
3128 | // We did it!
3129 | putVariable(curResult,1);
3130 | }
3131 |
3132 | // RESTORE [baddr1 n baddr2] V5+
3133 | private void zop_ext_restore()
3134 | {
3135 | String fn;
3136 | String suggested;
3137 | FileInputStream fis;
3138 | DataInputStream dis;
3139 | int slen;
3140 |
3141 | // If there are no arguments, do a normal restore
3142 | if (numvops == 0) {
3143 | zop_restore();
3144 | return;
3145 | }
3146 |
3147 | // Get a filename to save under
3148 | suggested = null;
3149 | if ((numvops > 2) && (vops[2] != 0)) {
3150 | slen = memory.fetchByte(vops[2]);
3151 | if (slen > 0) {
3152 | StringBuffer tmp = new StringBuffer();
3153 | for (int i = 1;i <= slen;i++)
3154 | tmp.append(String.valueOf((char)memory.fetchByte(vops[2]+i)));
3155 | suggested = tmp.toString();
3156 | }
3157 | }
3158 | fn = zui.getFilename("Load Auxiliary File",suggested,false);
3159 | if (fn == null) { // An error-probably user cancelled.
3160 | putVariable(curResult,0);
3161 | return;
3162 | }
3163 |
3164 | try {
3165 | fis = new FileInputStream(fn);
3166 | dis = new DataInputStream(fis);
3167 | memory.readMemory(dis,vops[0],vops[1]);
3168 | fis.close();
3169 | }
3170 | catch (IOException ex1) {
3171 | putVariable(curResult,0);
3172 | return;
3173 | }
3174 |
3175 | // We did it!
3176 | if (version >= 3) {
3177 | putVariable(curResult,vops[1]);
3178 | }
3179 | }
3180 |
3181 | // LOG_SHIFT a s V5+
3182 | private void zop_log_shift()
3183 | {
3184 | int val;
3185 | int sop2;
3186 |
3187 | sop2 = signedWord(vops[1]);
3188 |
3189 | if (sop2 < 0)
3190 | val = vops[0] >>> Math.abs(sop2);
3191 | else
3192 | val = vops[0] << sop2;
3193 |
3194 | putVariable(curResult,val);
3195 | }
3196 |
3197 | // ART_SHIFT a s V5+
3198 | private void zop_art_shift()
3199 | {
3200 | int val;
3201 | int sop2;
3202 |
3203 | sop2 = signedWord(vops[1]);
3204 |
3205 | if (sop2 < 0) {
3206 | if (((voptypes[0] == ARGTYPE_WORD) && ((vops[0] & 0x8000) == 0x8000)) ||
3207 | ((voptypes[0] == ARGTYPE_BYTE) && ((vops[0] & 0x80) == 0x80)))
3208 | val = vops[0] >> Math.abs(sop2);
3209 | else
3210 | val = vops[0] >>> Math.abs(sop2);
3211 | } else
3212 | val = vops[0] << sop2;
3213 |
3214 | putVariable(curResult,val);
3215 | }
3216 |
3217 | // SET_FONT n V5,7-8
3218 | // SET_FONT n [window] V6
3219 | private void zop_set_font()
3220 | {
3221 | zui.setFont(vops[0]);
3222 | Dimension s = zui.getFontSize();
3223 | memory.putByte(0x26,s.height);
3224 | memory.putByte(0x27,s.width);
3225 | }
3226 |
3227 | // DRAW_PICTURE pic [y x] V6
3228 | private void zop_draw_picture()
3229 | {
3230 | zui.fatal("DRAW_PICTURE instruction unimplemented");
3231 | }
3232 |
3233 | // PICTURE_DATA pic baddr V6
3234 | private void zop_picture_data()
3235 | {
3236 | zui.fatal("PICTURE_DATA instruction unimplemented");
3237 | }
3238 |
3239 | // ERASE_PICTURE pic [y x] V6
3240 | private void zop_erase_picture()
3241 | {
3242 | zui.fatal("ERASE_PICTURE instruction unimplemented");
3243 | }
3244 |
3245 | // SET_MARGINS xl xr window V6
3246 | private void zop_set_margins()
3247 | {
3248 | zui.fatal("SET_MARGINS instruction unimplemented");
3249 | }
3250 |
3251 | // SAVE_UNDO V5+
3252 | private void zop_save_undo()
3253 | {
3254 | ByteArrayOutputStream bos;
3255 | DataOutputStream dos;
3256 |
3257 | try {
3258 | bos = new ByteArrayOutputStream(65536);
3259 | dos = new DataOutputStream(bos);
3260 | dumpState(dos);
3261 | memory.dumpMemory(dos,0,dynamicMemorySize);
3262 | undoState = bos.toByteArray();
3263 | }
3264 | catch (IOException ioex) {
3265 | zui.fatal("I/O exception during SAVE_UNDO??");
3266 | }
3267 |
3268 | // We did it!
3269 | if (version <= 3)
3270 | doBranch();
3271 | else
3272 | putVariable(curResult,1);
3273 | }
3274 |
3275 | // RESTORE_UNDO V5+
3276 | private void zop_restore_undo()
3277 | {
3278 | ByteArrayInputStream bis;
3279 | DataInputStream dis;
3280 | int tsBit;
3281 |
3282 | // Remember the transcript bit
3283 | tsBit = memory.fetchWord(0x10) & 0x0001;
3284 |
3285 | try {
3286 | bis = new ByteArrayInputStream(undoState);
3287 | dis = new DataInputStream(bis);
3288 | readState(dis);
3289 | memory.readMemory(dis,0,dynamicMemorySize);
3290 | }
3291 | catch (IOException ex1) {
3292 | zui.fatal("I/O Exception during RESTORE_UNDO???");
3293 | }
3294 |
3295 | // We did it!
3296 | memory.putWord(0x10,memory.fetchWord(0x10) | tsBit);
3297 | if (version >= 3) {
3298 | curResult = memory.fetchByte(curCallFrame.pc - 1);
3299 | putVariable(curResult,2); // Is this correct?
3300 | }
3301 | }
3302 |
3303 | // MOVE_WINDOW window y x V6
3304 | private void zop_move_window()
3305 | {
3306 | zui.fatal("MOVE_WINDOW instruction unimplemented");
3307 | }
3308 |
3309 | // WINDOW_SIZE window y x V6
3310 | private void zop_window_size()
3311 | {
3312 | zui.fatal("WINDOW_SIZE instruction unimplemented");
3313 | }
3314 |
3315 | // WINDOW_STYLE window flags op V6
3316 | private void zop_window_style()
3317 | {
3318 | zui.fatal("WINDOW_STYLE instruction unimplemented");
3319 | }
3320 |
3321 | // GET_WIND_PROP window p V6
3322 | private void zop_get_wind_prop()
3323 | {
3324 | zui.fatal("GET_WINDOW_PROP instruction unimplemented");
3325 | }
3326 |
3327 | // SCROLL_WINDOW window s V6
3328 | private void zop_scroll_window()
3329 | {
3330 | zui.fatal("SCROLL_WINDOW instruction unimplemented");
3331 | }
3332 |
3333 | // POP_STACK n [baddr] V6
3334 | private void zop_pop_stack()
3335 | {
3336 | zui.fatal("POP_STACK instruction unimplemented");
3337 | }
3338 |
3339 | // READ_MOUSE baddr V6
3340 | private void zop_read_mouse()
3341 | {
3342 | zui.fatal("READ_MOUSE instruction unimplemented");
3343 | }
3344 |
3345 | // MOUSE_WINDOW window V6
3346 | private void zop_mouse_window()
3347 | {
3348 | zui.fatal("MOUSE_WINDOW instruction unimplemented");
3349 | }
3350 |
3351 | // PUSH_STACK a baddr V6
3352 | private void zop_push_stack()
3353 | {
3354 | zui.fatal("PUSH_STACK instruction unimplemented");
3355 | }
3356 |
3357 | // PUT_WIND_PROP window p a V6
3358 | private void zop_put_wind_prop()
3359 | {
3360 | zui.fatal("PUT_WIND_PROP instruction unimplemented");
3361 | }
3362 |
3363 | // PRINT_FORM baddr V6
3364 | private void zop_print_form()
3365 | {
3366 | zui.fatal("PRINT_FORM instruction unimplemented");
3367 | }
3368 |
3369 | // MAKE_MENU n baddr V6
3370 | private void zop_make_menu()
3371 | {
3372 | zui.fatal("MAKE_MENU instruction unimplemented");
3373 | }
3374 |
3375 | // PICTURE_TABLE baddr V6
3376 | private void zop_picture_table()
3377 | {
3378 | zui.fatal("PICTURE_TABLE instruction unimplemented");
3379 | }
3380 | }
3381 |
--------------------------------------------------------------------------------