View Javadoc

1   /*
2    * $Id: PortableGameState.java,v 1.6 2005/11/03 19:06:29 weiju Exp $
3    * 
4    * Created on 03.10.2005
5    * Copyright 2005 by Wei-ju Wu
6    *
7    * This file is part of The Z-machine Preservation Project (ZMPP).
8    *
9    * ZMPP is free software; you can redistribute it and/or modify
10   * it under the terms of the GNU General Public License as published by
11   * the Free Software Foundation; either version 2 of the License, or
12   * (at your option) any later version.
13   *
14   * ZMPP is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Public License for more details.
18   *
19   * You should have received a copy of the GNU General Public License
20   * along with ZMPP; if not, write to the Free Software
21   * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
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   // ***** Reading the state from a file
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     // read release number
198     release = chunkMem.readUnsignedShort(offset);
199     offset += 2;
200     
201     // read serial number
202     for (int i = 0; i < 6; i++) {
203       
204       serialBytes[i] = chunkMem.readByte(offset + i);
205     }
206     offset += 6;
207     
208     // read check sum
209     checksum = chunkMem.readUnsignedShort(offset);
210     offset += 2;
211 
212     // read pc
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       System.out.println("pc: " + stackFrame.pc + " numLocals: " + numLocals + ", evalStackSize: " + evalStackSize
248           + " retvar: " + stackFrame.returnVariable);
249           */
250       stackFrame.evalStack = new short[evalStackSize];
251       offset += 2;
252     
253       // Read local variables
254       for (int i = 0; i < numLocals; i++) {
255       
256         stackFrame.locals[i] = chunkMem.readShort(offset);
257         offset += 2;
258       }
259     
260       // Read evaluation stack values
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++) { // (runlength + 1) iterations
311           
312           byteBuffer.add((byte) 0);
313         }
314       } else {
315         
316         byteBuffer.add(b);
317       }
318     }
319     
320     // Copy the results to the delta array
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   // ***** Reading the state from a Machine
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     // capture dynamic memory which ends at address(staticsMem) - 1
365     // uncompressed
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     //machine.getR
381     // TODO: Write out stack frames
382   }
383 
384   // ***********************************************************************
385   // ******* Helpers
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     //System.out.println("argSpec: " + argspec);
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 }