cfad47cfa3/tads3/vmsave.cpp

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
#ifdef RCSID
2
static char RCSid[] =
3
"$Header$";
4
#endif
5
6
/* 
7
 *   Copyright (c) 1999, 2002 Michael J. Roberts.  All Rights Reserved.
8
 *   
9
 *   Please see the accompanying license file, LICENSE.TXT, for information
10
 *   on using and copying this software.  
11
 */
12
/*
13
Name
14
  vmsave.cpp - T3 save/restore state
15
Function
16
  
17
Notes
18
  
19
Modified
20
  08/02/99 MJRoberts  - Creation
21
*/
22
23
#include "t3std.h"
24
#include "os.h"
25
#include "vmglob.h"
26
#include "vmsave.h"
27
#include "vmfile.h"
28
#include "vmimage.h"
29
#include "vmobj.h"
30
#include "vmrun.h"
31
#include "vmstack.h"
32
#include "vmundo.h"
33
#include "vmmeta.h"
34
#include "vmcrc.h"
35
36
37
/* ------------------------------------------------------------------------ */
38
/*
39
 *   Saved state signature.  This signature is tied to a specific format
40
 *   version; it should be changed whenever the format version is modified
41
 *   so that it is incompatible with older versions.
42
 *   
43
 *   Note that incompatible changes to intrinsic class serialization formats
44
 *   will require updating the version number.
45
 *   
46
 *   Incompatible changes to the format are not a particularly big deal.
47
 *   Saved states tend to be local to a particular machine, since they're
48
 *   mostly used to suspend sessions for later resumption and to "branch"
49
 *   the state evolution (i.e., to allow playing a game from a particular
50
 *   point, then returning later to that same point to play again, but doing
51
 *   different things this time; this is used particularly to save "good"
52
 *   positions as a precaution against later getting into unwinnable
53
 *   states).  
54
 */
55
#define VMSAVEFILE_SIG "T3-state-v0009\015\012\032"
56
57
58
/* ------------------------------------------------------------------------ */
59
/*
60
 *   Compute the checksum of the contents of a file stream.  We'll compute
61
 *   the checksum of the given file, starting at the current seek position
62
 *   and running for the requested number of bytes.  
63
 */
64
static unsigned long compute_checksum(CVmFile *fp, unsigned long len)
65
{
66
    CVmCRC32 crc;
67
68
    /* read the file and compute the CRC value for its contents */
69
    while (len != 0)
70
    {
71
        char buf[256];
72
        size_t cur_len;
73
        
74
        /* figure out how much we can load from the file */
75
        cur_len = sizeof(buf);
76
        if (cur_len > len)
77
            cur_len = (size_t)len;
78
79
        /* load the data from the file */
80
        fp->read_bytes(buf, cur_len);
81
82
        /* deduct the amount we read from the overall file length remaining */
83
        len -= cur_len;
84
85
        /* scan this block into the checksum */
86
        crc.scan_bytes(buf, cur_len);
87
    }
88
89
    /* return the computed value */
90
    return crc.get_crc_val();
91
}
92
93
94
/* ------------------------------------------------------------------------ */
95
/*
96
 *   Save VM state to a file 
97
 */
98
void CVmSaveFile::save(VMG_ CVmFile *fp)
99
{
100
    const char *fname;
101
    size_t fname_len;
102
    long startpos;
103
    long endpos;
104
    unsigned long crcval;
105
    unsigned long datasize;
106
    
107
    /* write the signature */
108
    fp->write_bytes(VMSAVEFILE_SIG, sizeof(VMSAVEFILE_SIG)-1);
109
110
    /* note the seek position of the start of the file header */
111
    startpos = fp->get_pos();
112
113
    /* write a placeholder for the stream size and checksum */
114
    fp->write_int4(0);
115
    fp->write_int4(0);
116
117
    /* write the image file's timestamp */
118
    fp->write_bytes(G_image_loader->get_timestamp(), 24);
119
120
    /* get the image filename */
121
    fname = G_image_loader->get_filename();
122
    fname_len = strlen(fname);
123
124
    /* 
125
     *   write the image filename, so we can figure out what image file to
126
     *   load if we start the interpreter specifying only the saved state
127
     *   to be restored 
128
     */
129
    fp->write_int2(strlen(fname));
130
    fp->write_bytes(fname, fname_len);
131
132
    /* save all modified object state */
133
    G_obj_table->save(vmg_ fp);
134
135
    /* save the synthesized exports */
136
    G_image_loader->save_synth_exports(vmg_ fp);
137
138
    /* remember where the file ends */
139
    endpos = fp->get_pos();
140
141
    /* 
142
     *   compute the size of the data stream - this includes everything
143
     *   after the size/checksum fields 
144
     */
145
    datasize = endpos - startpos - 8;
146
147
    /* 
148
     *   seek back to just after the size/checksum header - this is the
149
     *   start of the section of the file for which we must compute the
150
     *   checksum 
151
     */
152
    fp->set_pos(startpos + 8);
153
154
    /* compute the checksum */
155
    crcval = compute_checksum(fp, datasize);
156
157
    /* 
158
     *   seek back to the size/checksum header, and fill in those fields now
159
     *   that we know their values 
160
     */
161
    fp->set_pos(startpos);
162
    fp->write_int4(datasize);
163
    fp->write_int4(crcval);
164
165
    /* seek back to the end of the file */
166
    fp->set_pos(endpos);
167
}
168
169
/* ------------------------------------------------------------------------ */
170
/*
171
 *   Given a saved state file, get the name of the image file that was
172
 *   loaded when the state file was created. 
173
 */
174
int CVmSaveFile::restore_get_image(osfildef *fp,
175
                                   char *fname_buf, size_t fname_buf_len)
176
{
177
    char buf[128];
178
    size_t len;
179
180
    /* read the signature, size/checksum, and timestamp fields */
181
    if (osfrb(fp, buf, sizeof(VMSAVEFILE_SIG)-1 + 8 + 24))
182
        return VMERR_READ_FILE;
183
184
    /* check the signature */
185
    if (memcmp(buf, VMSAVEFILE_SIG, sizeof(VMSAVEFILE_SIG)-1) != 0)
186
        return VMERR_NOT_SAVED_STATE;
187
188
    /* read the length of the image file name */
189
    if (osfrb(fp, buf, 2))
190
        return VMERR_READ_FILE;
191
192
    /* get the length from the buffer */
193
    len = osrp2(buf);
194
195
    /* if it won't fit in the buffer, return an error */
196
    if (len + 1 > fname_buf_len)
197
        return VMERR_READ_FILE;
198
199
    /* read the name into the caller's buffer */
200
    if (osfrb(fp, fname_buf, len))
201
        return VMERR_READ_FILE;
202
203
    /* null-terminate the name */
204
    fname_buf[len] = '\0';
205
206
    /* success */
207
    return 0;
208
}
209
210
/* ------------------------------------------------------------------------ */
211
/*
212
 *   Restore VM state from a file.  Returns zero on success, non-zero on
213
 *   error.  
214
 */
215
int CVmSaveFile::restore(VMG_ CVmFile *fp)
216
{
217
    char buf[128];
218
    int err;
219
    unsigned long datasize;
220
    unsigned long old_crcval;
221
    unsigned long new_crcval;
222
    long startpos;
223
    int old_gc_enabled;
224
    CVmObjFixup *fixups;
225
226
    /* we don't have a fixup table yet (the object loader will create one) */
227
    fixups = 0;
228
229
    /* read the file's signature */
230
    fp->read_bytes(buf, sizeof(VMSAVEFILE_SIG)-1);
231
232
    /* check the signature */
233
    if (memcmp(buf, VMSAVEFILE_SIG, sizeof(VMSAVEFILE_SIG)-1) != 0)
234
        return VMERR_NOT_SAVED_STATE;
235
236
    /* read the size/checksum fields */
237
    datasize = fp->read_uint4();
238
    old_crcval = fp->read_uint4();
239
240
    /* note the starting position of the datastream */
241
    startpos = fp->get_pos();
242
243
    /* compute the checksum of the file data */
244
    new_crcval = compute_checksum(fp, datasize);
245
246
    /* 
247
     *   if the checksum we computed doesn't match the one stored in the
248
     *   file, the file is corrupted 
249
     */
250
    if (new_crcval != old_crcval)
251
        return VMERR_BAD_SAVED_STATE;
252
253
    /* seek back to the starting position */
254
    fp->set_pos(startpos);
255
256
    /* check the timestamp */
257
    fp->read_bytes(buf, 24);
258
    if (memcmp(buf, G_image_loader->get_timestamp(), 24) != 0)
259
        return VMERR_WRONG_SAVED_STATE;
260
261
    /* 
262
     *   skip the image filename - since we already have an image file
263
     *   loaded, this information is of no use to us here (it's only
264
     *   useful when we want to restore a saved state before we know what
265
     *   the image file is) 
266
     */
267
    fp->set_pos_from_cur(fp->read_int2());
268
269
    /* 
270
     *   discard all undo information - any undo information we currently
271
     *   have obviously can't be applied to the restored state 
272
     */
273
    G_undo->drop_undo(vmg0_);
274
275
    /* 
276
     *   Disable garbage collection while restoring.  This is necessary
277
     *   because there are possible intermediate states where we have
278
     *   restored some of the objects but not all of them, so objects that
279
     *   are reachable from the fully restored state won't necessarily appear
280
     *   to be reachable from all possible intermediate states. 
281
     */
282
    old_gc_enabled = G_obj_table->enable_gc(vmg_ FALSE);
283
284
    err_try
285
    {
286
        /* forget any IntrinsicClass instances we created at startup */
287
        G_meta_table->forget_intrinsic_class_instances(vmg0_);
288
289
        /* load the object data from the file */
290
        if ((err = G_obj_table->restore(vmg_ fp, &fixups)) != 0)
291
            goto read_done;
292
        
293
        /* load the synthesized exports from the file */
294
        err = G_image_loader->restore_synth_exports(vmg_ fp, fixups);
295
        if (err != 0)
296
            goto read_done;
297
298
        /* 
299
         *   re-link to the exports and synthesized exports loaded from the
300
         *   saved session 
301
         */
302
        G_image_loader->do_dynamic_link(vmg0_);
303
304
        /* create any missing IntrinsicClass instances */
305
        G_meta_table->create_intrinsic_class_instances(vmg0_);
306
307
        /* perform any requested post-load object initializations */
308
        G_obj_table->do_all_post_load_init(vmg0_);
309
310
    read_done: ;
311
    }
312
    err_catch(exc)
313
    {
314
        /* remember the error code */
315
        err = exc->get_error_code();
316
    }
317
    err_end;
318
319
    /* we're done with the fixup table, so delete it if we created one */
320
    if (fixups != 0)
321
        delete fixups;
322
323
    /* restore the garbage collector's enabled state */
324
    G_obj_table->enable_gc(vmg_ old_gc_enabled);
325
326
    /* 
327
     *   explicitly run garbage collection, since any dynamic objects that
328
     *   were reachable before the restore only through non-transient
329
     *   references will no longer be reachable, all of the non-transient
330
     *   references having been replaced now 
331
     */
332
    G_obj_table->gc_full(vmg0_);
333
334
    /* if any error occurred, throw the error */
335
    if (err != 0)
336
        err_throw(err);
337
338
    /* success */
339
    return 0;
340
}
341
342
/* ------------------------------------------------------------------------ */
343
/*
344
 *   Reset to initial image file state 
345
 */
346
void CVmSaveFile::reset(VMG0_)
347
{
348
    /* 
349
     *   discard undo information, since it applies only to the current VM
350
     *   state and obviously is no longer relevant after we reset to the
351
     *   initial state 
352
     */
353
    G_undo->drop_undo(vmg0_);
354
355
    /* 
356
     *   discard all synthesized exports, since we want to dynamically link
357
     *   to the base image file state 
358
     */
359
    G_image_loader->discard_synth_exports();
360
361
    /* forget any IntrinsicClass instances we created at startup */
362
    G_meta_table->forget_intrinsic_class_instances(vmg0_);
363
364
    /* reset all objects to initial image file load state */
365
    G_obj_table->reset_to_image(vmg0_);
366
367
    /* 
368
     *   forget the previous dynamic linking information and relink to the
369
     *   image file again - this will ensure that any objects created after
370
     *   load are properly re-created now 
371
     */
372
    G_image_loader->do_dynamic_link(vmg0_);
373
374
    /* create any missing IntrinsicClass instances */
375
    G_meta_table->create_intrinsic_class_instances(vmg0_);
376
377
    /* perform any requested post-load object initializations */
378
    G_obj_table->do_all_post_load_init(vmg0_);
379
380
    /* 
381
     *   explicitly run garbage collection to clean up dynamic objects that
382
     *   are no longer reachable from the initial state
383
     */
384
    G_obj_table->gc_full(vmg0_);
385
386
    /* run the static initializers */
387
    G_image_loader->run_static_init(vmg0_);
388
}
389