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.iff.Chunk;
30 import org.zmpp.iff.FormChunk;
31
32 /***
33 * This class represents the state of the Z machine in an external format,
34 * so it can be exchanged using the Quetzal IFF format.
35 *
36 * @author Wei-ju Wu
37 * @version 1.0
38 */
39 public class PortableGameState {
40
41 /***
42 * This class represents a stack frame in the portable game state model.
43 */
44 public static class StackFrame {
45
46 int pc;
47 int returnVariable;
48 short[] locals;
49 short[] evalStack;
50 int[] args;
51
52 public int getProgramCounter() { return pc; }
53 public int getReturnVariable() { return returnVariable; }
54 public short[] getEvalStack() { return evalStack; }
55 public short[] getLocals() { return locals; }
56 public int[] getArgs() { return args; }
57 }
58
59 /***
60 * The release number.
61 */
62 private int release;
63
64 /***
65 * The story file checksum.
66 */
67 private int checksum;
68
69 /***
70 * The serial number.
71 */
72 private byte[] serialBytes;
73
74 /***
75 * The program counter.
76 */
77 private int pc;
78
79 /***
80 * The uncompressed dynamic memory.
81 */
82 private byte[] dynamicMem;
83
84 /***
85 * The delta.
86 */
87 private byte[] delta;
88
89 /***
90 * The list of stack frames in this game state, from oldest to latest.
91 */
92 private List<StackFrame> stackFrames;
93
94 /***
95 * Constructor.
96 */
97 public PortableGameState() {
98
99 serialBytes = new byte[6];
100 stackFrames = new ArrayList<StackFrame>();
101 }
102
103 /***
104 * Returns the game release number.
105 *
106 * @return the release number
107 */
108 public int getRelease() { return release; }
109
110 /***
111 * Returns the game checksum.
112 *
113 * @return the checksum
114 */
115 public int getChecksum() { return checksum; }
116
117 /***
118 * Returns the game serial number.
119 *
120 * @return the serial number
121 */
122 public String getSerialNumber() { return new String(serialBytes); }
123
124 /***
125 * Returns the program counter.
126 *
127 * @return the program counter
128 */
129 public int getProgramCounter() { return pc; }
130
131 /***
132 * Returns the list of stack frames.
133 *
134 * @return the stack frames
135 */
136 public List<StackFrame> getStackFrames() {
137
138 return stackFrames;
139 }
140
141 /***
142 * Returns the delta bytes. This is the changes in dynamic memory, where
143 * 0 represents no change.
144 *
145 * @return the delta bytes
146 */
147 public byte[] getDeltaBytes() {
148
149 return delta;
150 }
151
152 /***
153 * Returns the current dump of dynamic memory captured from a Machine object.
154 *
155 * @return the dynamic memory dump
156 */
157 public byte[] getDynamicMemoryDump() {
158
159 return dynamicMem;
160 }
161
162
163
164
165 /***
166 * Initialize the state from an IFF form.
167 *
168 * @param formChunk the IFF form
169 * @return false if there was a consistency problem during the read
170 */
171 public boolean readSaveGame(FormChunk formChunk) {
172
173 stackFrames.clear();
174
175 if ((new String(formChunk.getSubId())).equals("IFZS")) {
176
177 readIfhdChunk(formChunk);
178 readStacksChunk(formChunk);
179 readMemoryChunk(formChunk);
180
181 return true;
182 }
183 return false;
184 }
185
186 /***
187 * Evaluate the contents of the IFhd chunk.
188 *
189 * @param formChunk the FORM chunk
190 */
191 private void readIfhdChunk(FormChunk formChunk) {
192
193 Chunk ifhdChunk = formChunk.getSubChunk("IFhd".getBytes());
194 MemoryAccess chunkMem = ifhdChunk.getMemoryAccess();
195 int offset = Chunk.CHUNK_HEADER_LENGTH;
196
197
198 release = chunkMem.readUnsignedShort(offset);
199 offset += 2;
200
201
202 for (int i = 0; i < 6; i++) {
203
204 serialBytes[i] = chunkMem.readByte(offset + i);
205 }
206 offset += 6;
207
208
209 checksum = chunkMem.readUnsignedShort(offset);
210 offset += 2;
211
212
213 pc = decodePcBytes(chunkMem.readByte(offset), chunkMem.readByte(offset + 1),
214 chunkMem.readByte(offset + 2));
215 }
216
217 /***
218 * Evalutate the contents of the Stks chunk.
219 *
220 * @param formChunk the FORM chunk
221 */
222 private void readStacksChunk(FormChunk formChunk) {
223
224 Chunk stksChunk = formChunk.getSubChunk("Stks".getBytes());
225 MemoryAccess chunkMem = stksChunk.getMemoryAccess();
226 int offset = Chunk.CHUNK_HEADER_LENGTH;
227 int chunksize = stksChunk.getSize() + Chunk.CHUNK_HEADER_LENGTH;
228
229 while (offset < chunksize) {
230
231 StackFrame stackFrame = new StackFrame();
232 stackFrame.pc = decodePcBytes(chunkMem.readByte(offset),
233 chunkMem.readByte(offset + 1), chunkMem.readByte(offset + 2));
234 offset += 3;
235
236 byte pvFlags = chunkMem.readByte(offset++);
237 int numLocals = getNumLocals(pvFlags);
238 stackFrame.locals = new short[numLocals];
239
240 stackFrame.returnVariable = chunkMem.readByte(offset++);
241
242 byte argSpec = chunkMem.readByte(offset++);
243 stackFrame.args = getArgs(argSpec);
244
245 int evalStackSize = chunkMem.readUnsignedShort(offset);
246
247
248
249
250 stackFrame.evalStack = new short[evalStackSize];
251 offset += 2;
252
253
254 for (int i = 0; i < numLocals; i++) {
255
256 stackFrame.locals[i] = chunkMem.readShort(offset);
257 offset += 2;
258 }
259
260
261 for (int i = 0; i < evalStackSize; i++) {
262
263 stackFrame.evalStack[i] = chunkMem.readShort(offset);
264 offset += 2;
265 }
266 stackFrames.add(stackFrame);
267 }
268 }
269
270 /***
271 * Evaluate the contents of the Cmem and the UMem chunks.
272 *
273 * @param formChunk the FORM chunk
274 */
275 private void readMemoryChunk(FormChunk formChunk) {
276
277 Chunk cmemChunk = formChunk.getSubChunk("CMem".getBytes());
278 Chunk umemChunk = formChunk.getSubChunk("UMem".getBytes());
279 if (cmemChunk != null) {
280
281 readCMemChunk(cmemChunk);
282
283 } else if (umemChunk != null) {
284
285 readUMemChunk(umemChunk);
286 }
287 }
288
289 /***
290 * Decompresses and reads the dynamic memory state.
291 *
292 * @param cmemChunk the CMem chunk
293 */
294 private void readCMemChunk(Chunk cmemChunk) {
295
296 MemoryAccess chunkMem = cmemChunk.getMemoryAccess();
297 int offset = Chunk.CHUNK_HEADER_LENGTH;
298 int chunksize = cmemChunk.getSize() + Chunk.CHUNK_HEADER_LENGTH;
299 List<Byte> byteBuffer = new ArrayList<Byte>();
300
301 byte b;
302
303 while (offset < chunksize) {
304
305 b = chunkMem.readByte(offset++);
306 if (b == 0) {
307
308 short runlength = chunkMem.readUnsignedByte(offset++);
309
310 for (int r = 0; r <= runlength; r++) {
311
312 byteBuffer.add((byte) 0);
313 }
314 } else {
315
316 byteBuffer.add(b);
317 }
318 }
319
320
321 delta = new byte[byteBuffer.size()];
322 for (int i = 0; i < delta.length; i++) {
323
324 delta[i] = byteBuffer.get(i);
325 }
326 }
327
328 /***
329 * Reads the uncompressed dynamic memory state.
330 *
331 * @param umemChunk the UMem chunk
332 */
333 private void readUMemChunk(Chunk umemChunk) {
334
335 MemoryAccess chunkMem = umemChunk.getMemoryAccess();
336 int datasize = umemChunk.getSize();
337
338 dynamicMem = new byte[datasize];
339 for (int i = 0; i < datasize; i++) {
340
341 dynamicMem[i] = chunkMem.readByte(i + Chunk.CHUNK_HEADER_LENGTH);
342 }
343 }
344
345
346
347
348
349 /***
350 * Makes a snapshot of the current machine state. The savePc argument
351 * is taken as the restore program counter.
352 *
353 * @param machine a Machine
354 * @param savePc the program counter restore value
355 */
356 public void captureMachineState(Machine machine, int savePc) {
357
358 StoryFileHeader fileheader = machine.getStoryFileHeader();
359 release = fileheader.getRelease();
360 checksum = fileheader.getChecksum();
361 serialBytes = fileheader.getSerialNumber().getBytes();
362 pc = savePc;
363
364
365
366 MemoryAccess memaccess = machine.getMemoryAccess();
367 int staticMemStart = fileheader.getStaticsAddress();
368 dynamicMem = new byte[staticMemStart];
369
370 for (int i = 0; i < staticMemStart; i++) {
371
372 dynamicMem[i] = memaccess.readByte(i);
373 }
374
375 captureStackFrames(machine);
376 }
377
378 private void captureStackFrames(Machine machine) {
379
380
381
382 }
383
384
385
386
387
388 /***
389 * There is no apparent reason at the moment to implement getArgs().
390 *
391 * @param argspec the argspec byte
392 * @return the specified arguments
393 */
394 private int[] getArgs(byte argspec) {
395
396
397 int andBit;
398 List<Integer> result = new ArrayList<Integer>();
399
400 for (int i = 0; i < 7; i++) {
401
402 andBit = 1 << i;
403 if ((andBit & argspec) > 0) result.add(i);
404
405 }
406 int[] intArray = new int[result.size()];
407 for (int i = 0; i < result.size(); i++) {
408
409 intArray[i] = result.get(i);
410 }
411 return intArray;
412 }
413
414 /***
415 * Mask out the lower 4 bits if Bit 5 is cleared.
416 *
417 * @param pvFlags a bit mask of the form xxxpvvvv
418 * @return 0000vvvv if p is cleared, 0 otherwise
419 */
420 private int getNumLocals(byte pvFlags) {
421
422 if ((pvFlags & 0x10) == 0) {
423
424 return pvFlags & 0x0f;
425 }
426 return 0;
427 }
428
429 /***
430 * Joins three bytes to a program counter value.
431 *
432 * @param b0 byte 0
433 * @param b1 byte 1
434 * @param b2 byte 2
435 * @return the resulting program counter
436 */
437 private int decodePcBytes(byte b0, byte b1, byte b2) {
438
439 return ((b0 & 0xff) << 16) | ((b1 & 0xff) << 8) | (b2 & 0xff);
440 }
441 }