1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 package org.zmpp.vm;
24
25 import java.util.ArrayList;
26 import java.util.List;
27
28 import org.zmpp.base.MemoryAccess;
29 import org.zmpp.vmutil.PredictableRandomGenerator;
30 import org.zmpp.vmutil.RandomGenerator;
31 import org.zmpp.vmutil.UnpredictableRandomGenerator;
32 import org.zmpp.vmutil.ZsciiConverter;
33 import org.zmpp.vmutil.ZsciiString;
34 import org.zmpp.vmutil.ZsciiConverter.Alphabet;
35
36 /***
37 * This class implements the state and some services of a Z-machine, version 3.
38 *
39 * @author Wei-ju Wu
40 * @version 1.0
41 */
42 public class Machine3 implements Machine {
43
44 /***
45 * The memory access object.
46 */
47 private MemoryAccess memaccess;
48
49 /***
50 * This machine's current program counter.
51 */
52 private int programCounter;
53
54 /***
55 * This machine's global stack.
56 */
57 private List<Short> stack;
58
59 /***
60 * The routine info.
61 */
62 private List<RoutineContext> routineContextStack;
63
64 /***
65 * The start of global variables.
66 */
67 private int globalsAddress;
68
69 /***
70 * The object tree.
71 */
72 private ObjectTree objectTree;
73
74 /***
75 * The dictionary.
76 */
77 private Dictionary dictionary;
78
79 /***
80 * This is the array of output streams.
81 */
82 private OutputStream[] outputStream;
83
84 /***
85 * This is the array of input streams.
86 */
87 private InputStream[] inputStream;
88
89 /***
90 * The selected input stream.
91 */
92 private int selectedInputStreamIndex;
93
94 /***
95 * This flag indicates the run status.
96 */
97 private boolean running;
98
99 /***
100 * The random generator.
101 */
102 private RandomGenerator random;
103
104 /***
105 * The instruction decoder.
106 */
107 private InstructionDecoder decoder;
108
109 /***
110 * The status line.
111 */
112 private StatusLine statusLine;
113
114 /***
115 * Returns the checksum verification status of the story file.
116 */
117 private boolean hasValidChecksum;
118
119 /***
120 * The file header.
121 */
122 private StoryFileHeader fileHeader;
123
124 /***
125 * Constructor.
126 */
127 public Machine3() {
128
129 }
130
131 /***
132 * {@inheritDoc}
133 */
134 public void initialize(MemoryAccess memaccess, StoryFileHeader fileheader) {
135
136 this.memaccess = memaccess;
137 this.fileHeader = fileheader;
138 this.stack = new ArrayList<Short>();
139 this.routineContextStack = new ArrayList<RoutineContext>();
140 this.programCounter = fileheader.getProgramStart();
141 this.globalsAddress = fileheader.getGlobalsAddress();
142 this.objectTree =
143 new Objects(memaccess, fileheader.getObjectTableAddress());
144 this.outputStream = new OutputStream[3];
145 this.inputStream = new InputStream[2];
146 this.selectedInputStreamIndex = 1;
147 this.random = new UnpredictableRandomGenerator();
148 this.dictionary = new DefaultDictionary(memaccess,
149 fileheader.getDictionaryAddress());
150 this.decoder = new InstructionDecoder(this, memaccess);
151 this.running = true;
152 int checksum = calculateChecksum(fileheader);
153 hasValidChecksum = fileheader.getChecksum() == checksum;
154 }
155
156 /***
157 * {@inheritDoc}
158 */
159 public StoryFileHeader getStoryFileHeader() {
160
161 return fileHeader;
162 }
163
164 /***
165 * Calculates the checksum of the file.
166 *
167 * @param fileheader the file header
168 * @return the check sum
169 */
170 private int calculateChecksum(StoryFileHeader fileheader) {
171
172 int filelen = fileheader.getFileLength();
173 int sum = 0;
174
175 for (int i = 0x40; i < filelen; i++) {
176
177 sum += memaccess.readUnsignedByte(i);
178 }
179 return (sum & 0xffff);
180 }
181
182 /***
183 * {@inheritDoc}
184 */
185 public boolean hasValidChecksum() {
186
187 return this.hasValidChecksum;
188 }
189
190 /***
191 * {@inheritDoc}
192 */
193 public MemoryAccess getMemoryAccess() {
194
195 return memaccess;
196 }
197
198 /***
199 * {@inheritDoc}
200 */
201 public Dictionary getDictionary() {
202
203 return dictionary;
204 }
205
206 public ObjectTree getObjectTree() {
207
208 return objectTree;
209 }
210
211 /***
212 * Returns the global stack pointer. This function is mainly exposed for
213 * testing purposes.
214 *
215 * @return the stack pointer
216 */
217 public int getStackPointer() {
218
219 return stack.size();
220 }
221
222 /***
223 * {@inheritDoc}
224 */
225 public void setStackPointer(int stackpointer) {
226
227
228 int diff = getStackPointer() - stackpointer;
229 for (int i = 0; i < diff; i++) {
230
231 stack.remove(stack.size() - 1);
232 }
233 }
234
235 /***
236 * {@inheritDoc}
237 */
238 public short getStackTopElement() {
239
240 if (stack.size() > 0) {
241
242 return stack.get(stack.size() - 1);
243 }
244 return -1;
245 }
246
247 public void setStackTopElement(short value) {
248
249 stack.set(stack.size() - 1, value);
250 }
251
252 /***
253 * {@inheritDoc}
254 */
255 public int getProgramCounter() {
256
257 return programCounter;
258 }
259
260 /***
261 * {@inheritDoc}
262 */
263 public void setProgramCounter(int address) {
264
265 this.programCounter = address;
266 }
267
268 /***
269 * {@inheritDoc}
270 */
271 public short getVariable(int variableNumber) {
272
273 VariableType varType = getVariableType(variableNumber);
274 if (varType == VariableType.STACK) {
275
276 if (stack.size() == 0) {
277
278 throw new IllegalStateException("stack is empty");
279
280 } else {
281
282 return stack.remove(stack.size() - 1);
283 }
284
285 } else if (varType == VariableType.LOCAL) {
286
287 int localVarNumber = getLocalVariableNumber(variableNumber);
288 checkLocalVariableAccess(localVarNumber);
289 return getCurrentRoutineContext().getLocalVariable(localVarNumber);
290
291 } else {
292
293 return memaccess.readShort(globalsAddress
294 + (getGlobalVariableNumber(variableNumber) * 2));
295 }
296 }
297
298 /***
299 * {@inheritDoc}
300 */
301 public void setVariable(int variableNumber, short value) {
302
303 VariableType varType = getVariableType(variableNumber);
304 if (varType == VariableType.STACK) {
305
306 stack.add(value);
307
308 } else if (varType == VariableType.LOCAL) {
309
310 int localVarNumber = getLocalVariableNumber(variableNumber);
311 checkLocalVariableAccess(localVarNumber);
312 getCurrentRoutineContext().setLocalVariable(localVarNumber, value);
313
314 } else {
315
316 memaccess.writeShort(globalsAddress
317 + (getGlobalVariableNumber(variableNumber) * 2), value);
318 }
319 }
320
321 /***
322 * {@inheritDoc}
323 */
324 public void pushRoutineContext(RoutineContext routineContext) {
325
326 routineContext.setInvocationStackPointer(getStackPointer());
327 routineContextStack.add(routineContext);
328 }
329
330 /***
331 * {@inheritDoc}
332 */
333 public void popRoutineContext(short returnValue) {
334
335 if (routineContextStack.size() > 0) {
336
337 RoutineContext popped =
338 routineContextStack.remove(routineContextStack.size() - 1);
339
340
341 setStackPointer(popped.getInvocationStackPointer());
342 programCounter = popped.getReturnAddress();
343 setVariable(popped.getReturnVariable(), returnValue);
344 } else {
345
346 throw new IllegalStateException("no routine context active");
347 }
348 }
349
350 /***
351 * {@inheritDoc}
352 */
353 public RoutineContext getCurrentRoutineContext() {
354
355 if (routineContextStack.size() == 0) return null;
356 return routineContextStack.get(routineContextStack.size() - 1);
357 }
358
359 public int getRoutineStackPointer() {
360
361 return routineContextStack.size();
362 }
363
364 /***
365 * Returns the variable type for the given variable number.
366 *
367 * @param variableNumber the variable number
368 * @return STACK if stack variable, LOCAL if local variable, GLOBAL if global
369 */
370 public static VariableType getVariableType(int variableNumber) {
371
372 if (variableNumber == 0) return VariableType.STACK;
373 else if (variableNumber < 0x10) return VariableType.LOCAL;
374 else return VariableType.GLOBAL;
375 }
376
377 /***
378 * {@inheritDoc}
379 */
380 public short random(short range) {
381
382 if (range < 0) {
383
384 random = new PredictableRandomGenerator(-range);
385 return 0;
386
387 } else if (range == 0) {
388
389 random = new UnpredictableRandomGenerator();
390 return 0;
391 }
392 return (short) ((random.next() % range) + 1);
393 }
394
395
396
397
398
399 /***
400 * {@inheritDoc}
401 */
402 public void setOutputStream(int streamnumber, OutputStream stream) {
403
404 outputStream[streamnumber - 1] = stream;
405 }
406
407 /***
408 * {@inheritDoc}
409 */
410 public void printZsciiString(int address) {
411
412 print(new ZsciiString(memaccess, address).toString());
413 }
414
415 /***
416 * {@inheritDoc}
417 */
418 public void print(String str) {
419
420 for (int i = 0; i < outputStream.length; i++) {
421
422 if (outputStream[i] != null && outputStream[i].isEnabled()) {
423
424 outputStream[i].print(str);
425 }
426 }
427 }
428
429 /***
430 * {@inheritDoc}
431 */
432 public void newline() {
433
434 for (int i = 0; i < outputStream.length; i++) {
435
436 if (outputStream[i] != null && outputStream[i].isEnabled()) {
437
438 outputStream[i].newline();
439 }
440 }
441 }
442
443 /***
444 * {@inheritDoc}
445 */
446 public void printZchar(short zchar) {
447
448 String str = String.valueOf(ZsciiConverter.decode(Alphabet.A0, zchar));
449 print(str);
450 }
451
452 /***
453 * {@inheritDoc}
454 */
455 public void printNumber(short number) {
456
457 print(String.valueOf(number));
458 }
459
460 /***
461 * {@inheritDoc}
462 */
463 public void enableOutputStream(int streamnumber, boolean flag) {
464
465 outputStream[streamnumber - 1].setEnabled(flag);
466 }
467
468 /***
469 * {@inheritDoc}
470 */
471 public void setInputStream(int streamnumber, InputStream stream) {
472
473 inputStream[streamnumber] = stream;
474 }
475
476 /***
477 * {@inheritDoc}
478 */
479 public void selectInputStream(int streamnumber) {
480
481 selectedInputStreamIndex = streamnumber;
482 }
483
484 /***
485 * {@inheritDoc}
486 */
487 public void readLine(int address, int bufferlen) {
488
489 inputStream[selectedInputStreamIndex].readLine(memaccess, address,
490 bufferlen);
491 }
492
493 /***
494 * {@inheritDoc}
495 */
496 public int translatePackedAddress(int packedAddress) {
497
498 return packedAddress * 2;
499 }
500
501
502
503
504
505 public void updateStatusLine() {
506
507 if (statusLine != null) {
508
509 int objNum = getVariable(0x10);
510 ZObject obj = getObjectTree().getObject(objNum);
511 String objectName = (new ZsciiString(getMemoryAccess(),
512 obj.getPropertiesDescriptionAddress())).toString();
513
514 int score = getVariable(0x11);
515 int steps = getVariable(0x12);
516 statusLine.updateStatus(objectName, score, steps);
517 }
518 }
519
520 /***
521 * {@inheritDoc}
522 */
523 public void setStatusLine(StatusLine statusLine) {
524
525 this.statusLine = statusLine;
526 }
527
528 /***
529 * {@inheritDoc}
530 */
531 public void halt(String errormsg) {
532
533 print(errormsg);
534 running = false;
535 }
536
537 /***
538 * {@inheritDoc}
539 */
540 public boolean save() {
541
542 print("the save function is not implemented yet.");
543 return false;
544 }
545
546 /***
547 * {@inheritDoc}
548 */
549 public boolean restore() {
550
551 print("the restore function is not implemented yet.");
552 return false;
553 }
554
555 /***
556 * {@inheritDoc}
557 */
558 public void restart() {
559
560 print("the restart function is not implemented yet.");
561 }
562
563 /***
564 * {@inheritDoc}
565 */
566 public void quit() {
567
568 running = false;
569 }
570
571 /***
572 * {@inheritDoc}
573 */
574 public boolean isRunning() {
575
576 return running;
577 }
578
579 /***
580 * {@inheritDoc}
581 */
582 public void start() {
583
584 running = true;
585 }
586
587 public Instruction nextStep() {
588
589 Instruction instruction = decoder.decodeInstruction(getProgramCounter());
590 return instruction;
591 }
592
593
594
595
596 /***
597 * Returns the local variable number for a specified variable number.
598 *
599 * @param variableNumber the variable number in an operand (0x01-0x0f)
600 * @return the local variable number
601 */
602 private int getLocalVariableNumber(int variableNumber) {
603
604 return variableNumber - 1;
605 }
606
607 /***
608 * Returns the global variable for the specified variable number.
609 *
610 * @param variableNumber a variable number (0x10-0xff)
611 * @return the global variable number
612 */
613 private int getGlobalVariableNumber(int variableNumber) {
614
615 return variableNumber - 0x10;
616 }
617
618 /***
619 * This function throws an exception if a non-existing local variable
620 * is accessed on the current routine context or no current routine context
621 * is set.
622 *
623 * @param localVariableNumber the local variable number
624 */
625 private void checkLocalVariableAccess(int localVariableNumber) {
626
627 if (routineContextStack.size() == 0) {
628
629 throw new IllegalStateException("no routine context set");
630 }
631
632 if (localVariableNumber >= getCurrentRoutineContext().getNumLocalVariables()) {
633
634 throw new IllegalStateException("access to non-existent local variable: "
635 + localVariableNumber);
636 }
637 }
638 }