├── .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 | [![Deploy](https://www.herokucdn.com/deploy/button.png)](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 | --------------------------------------------------------------------------------