1 /*
2 * vicii-snapshot.c - Snapshot functionality for the MOS 6569 (VIC-II)
3 * emulation.
4 *
5 * Written by
6 * Ettore Perazzoli <ettore@comm2000.it>
7 * Andreas Boose <viceteam@t-online.de>
8 *
9 * DTV sections written by
10 * Hannu Nuotio <hannu.nuotio@tut.fi>
11 * Daniel Kahlin <daniel@kahlin.net>
12 *
13 * This file is part of VICE, the Versatile Commodore Emulator.
14 * See README for copyright notice.
15 *
16 * This program is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 2 of the License, or
19 * (at your option) any later version.
20 *
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * You should have received a copy of the GNU General Public License
27 * along with this program; if not, write to the Free Software
28 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
29 * 02111-1307 USA.
30 *
31 */
32
33 #include "vice.h"
34
35 #include <stdio.h>
36
37 #include "alarm.h"
38 #include "interrupt.h"
39 #include "log.h"
40 #include "mem.h"
41 #include "raster-sprite-status.h"
42 #include "raster-sprite.h"
43 #include "snapshot.h"
44 #include "types.h"
45 #include "vicii-irq.h"
46 #include "vicii-snapshot.h"
47 #include "vicii-sprites.h"
48 #include "vicii.h"
49 #include "viciitypes.h"
50 #include "vicii-mem.h"
51
52
53 /* Make sure all the VIC-II alarms are removed. This just makes it easier to
54 write functions for loading snapshot modules in other video chips without
55 caring that the VIC-II alarms are dispatched when they really shouldn't
56 be. */
vicii_snapshot_prepare(void)57 void vicii_snapshot_prepare(void)
58 {
59 vicii.fetch_clk = CLOCK_MAX;
60 alarm_unset(vicii.raster_fetch_alarm);
61 vicii.draw_clk = CLOCK_MAX;
62 alarm_unset(vicii.raster_draw_alarm);
63 vicii.raster_irq_clk = CLOCK_MAX;
64 alarm_unset(vicii.raster_irq_alarm);
65 }
66
67
68 /*
69
70 This is the format of the VIC-II snapshot module.
71
72 Name Type Size Description
73
74 AllowBadLines BYTE 1 flag: if true, bad lines can happen
75 BadLine BYTE 1 flag: this is a bad line
76 Blank BYTE 1 flag: draw lines in border color
77 ColorBuf BYTE 40 character memory buffer (loaded at bad line)
78 IdleState BYTE 1 flag: idle state enabled
79 LPTrigger BYTE 1 flag: light pen has been triggered
80 LPX BYTE 1 light pen X
81 LPY BYTE 1 light pen Y
82 MatrixBuf BYTE 40 video matrix buffer (loaded at bad line)
83 NewSpriteDmaMask BYTE 1 value for SpriteDmaMask after drawing
84 sprites
85 RamBase DWORD 1 pointer to the start of RAM seen by the VIC
86 RasterCycle BYTE 1 current vicii.raster cycle
87 RasterLine WORD 1 current vicii.raster line
88 Registers BYTE 80 VIC-II registers
89 DTVstuff x x DTV stuff goes here
90 SbCollMask BYTE 1 sprite-background collisions so far
91 SpriteDmaMask BYTE 1 sprites having DMA turned on
92 SsCollMask BYTE 1 sprite-sprite collisions so far
93 VBank BYTE 1 location of memory bank
94 Vc WORD 1 internal VIC-II counter
95 VcAdd BYTE 1 value to add to Vc at the end of this line
96 (vicii.mem_counter_inc)
97 VcBase WORD 1 internal VIC-II memory pointer
98 VideoInt BYTE 1 status of VIC-II IRQ (vicii.irq_status)
99
100 [Sprite section: (repeat 8 times)]
101
102 SpriteXMemPtr BYTE 1 sprite memory pointer
103 SpriteXMemPtrInc BYTE 1 value to add to the MemPtr after fetch
104 SpriteXExpFlipFlop BYTE 1 sprite expansion flip-flop
105
106 [Alarm section]
107 FetchEventTick DWORD 1 ticks for the next "fetch" (DMA) event
108 FetchEventType BYTE 1 type of event (0: matrix, 1: sprite check, 2: sprite fetch)
109
110 */
111
112 static char snap_module_name[] = "VIC-II";
113 #define SNAP_MAJOR 1
114 #define SNAP_MINOR 1
115
vicii_snapshot_write_module(snapshot_t * s)116 int vicii_snapshot_write_module(snapshot_t *s)
117 {
118 int i;
119 snapshot_module_t *m;
120
121 /* FIXME: Dispatch all events? */
122
123 m = snapshot_module_create (s, snap_module_name, SNAP_MAJOR, SNAP_MINOR);
124 if (m == NULL) {
125 return -1;
126 }
127
128 if (0
129 /* AllowBadLines */
130 || SMW_B(m, (uint8_t)vicii.allow_bad_lines) < 0
131 /* BadLine */
132 || SMW_B(m, (uint8_t)vicii.bad_line) < 0
133 /* Blank */
134 || SMW_B(m, (uint8_t)vicii.raster.blank_enabled) < 0
135 /* ColorBuf */
136 || SMW_BA(m, vicii.cbuf, 40) < 0
137 /* IdleState */
138 || SMW_B(m, (uint8_t)vicii.idle_state) < 0
139 /* LPTrigger */
140 || SMW_B(m, (uint8_t)vicii.light_pen.triggered) < 0
141 /* LPX */
142 || SMW_B(m, (uint8_t)vicii.light_pen.x) < 0
143 /* LPY */
144 || SMW_B(m, (uint8_t)vicii.light_pen.y) < 0
145 /* MatrixBuf */
146 || SMW_BA(m, vicii.vbuf, 40) < 0
147 /* NewSpriteDmaMask */
148 || SMW_B(m, vicii.raster.sprite_status->new_dma_msk) < 0
149 /* RamBase */
150 || SMW_DW(m, (uint32_t)(vicii.ram_base_phi1 - mem_ram)) < 0
151 /* RasterCycle */
152 || SMW_B(m, (uint8_t)(VICII_RASTER_CYCLE(maincpu_clk))) < 0
153 /* RasterLine */
154 || SMW_W(m, (uint16_t)(VICII_RASTER_Y(maincpu_clk))) < 0) {
155 goto fail;
156 }
157
158 for (i = 0; i < 0x50; i++) {
159 /* Registers */
160 if (SMW_B(m, vicii.regs[i]) < 0) {
161 goto fail;
162 }
163 }
164
165 if (0
166 /* DTV stuff */
167 || SMW_DW(m, (uint32_t)vicii.counta) < 0
168 || SMW_DW(m, (uint32_t)vicii.counta_mod) < 0
169 || SMW_DW(m, (uint32_t)vicii.counta_step) < 0
170 || SMW_DW(m, (uint32_t)vicii.countb) < 0
171 || SMW_DW(m, (uint32_t)vicii.countb_mod) < 0
172 || SMW_DW(m, (uint32_t)vicii.countb_step) < 0
173 || SMW_DW(m, (uint32_t)vicii.extended_enable) < 0
174 || SMW_DW(m, (uint32_t)vicii.extended_lockout) < 0
175 || SMW_DW(m, (uint32_t)vicii.badline_disable) < 0
176 || SMW_DW(m, (uint32_t)vicii.colorfetch_disable) < 0
177 || SMW_DW(m, (uint32_t)vicii.overscan) < 0
178 || SMW_DW(m, (uint32_t)vicii.high_color) < 0
179 || SMW_DW(m, (uint32_t)vicii.border_off) < 0
180 || SMW_DW(m, (uint32_t)vicii.raster_irq_offset) < 0
181 || SMW_DW(m, (uint32_t)vicii.raster_irq_prevent) < 0
182 || SMW_BA(m, vicii.dtvpalette, 256) < 0
183 /* SbCollMask */
184 || SMW_B(m, (uint8_t)vicii.sprite_background_collisions) < 0
185 /* SpriteDmaMask */
186 || SMW_B(m, (uint8_t)vicii.raster.sprite_status->dma_msk) < 0
187 /* SsCollMask */
188 || SMW_B(m, (uint8_t)vicii.sprite_sprite_collisions) < 0
189 /* VBank */
190 || SMW_W(m, (uint16_t)vicii.vbank_phi1) < 0
191 /* Vc */
192 || SMW_W(m, (uint16_t)vicii.mem_counter) < 0
193 /* VcInc */
194 || SMW_B(m, (uint8_t)vicii.mem_counter_inc) < 0
195 /* VcBase */
196 || SMW_W(m, (uint16_t)vicii.memptr) < 0
197 /* VideoInt */
198 || SMW_B(m, (uint8_t)vicii.irq_status) < 0) {
199 goto fail;
200 }
201
202 for (i = 0; i < 8; i++) {
203 if (0
204 /* SpriteXMemPtr */
205 || SMW_B(m, (uint8_t)vicii.raster.sprite_status->sprites[i].memptr) < 0
206 /* SpriteXMemPtrInc */
207 || SMW_B(m, (uint8_t)vicii.raster.sprite_status->sprites[i].memptr_inc) < 0
208 /* SpriteXExpFlipFlop */
209 || SMW_B(m, (uint8_t)vicii.raster.sprite_status->sprites[i].exp_flag) < 0) {
210 goto fail;
211 }
212 }
213
214 if (0
215 /* FetchEventTick */
216 || SMW_DW(m, vicii.fetch_clk - maincpu_clk) < 0
217 /* FetchEventType */
218 || SMW_B(m, (uint8_t)vicii.fetch_idx) < 0) {
219 goto fail;
220 }
221
222 /* Added in version 1.1 of the snapshot module */
223 /* using "ram_base-ram" is F***ing bullshit - what when external memory
224 is not mapped anywhere in ram[]? We should rather use some more generic
225 configuration info. But as we use it above in V1.0... :-(
226 AF 16jan2001 */
227 if (0
228 /* RamBase */
229 || SMW_DW(m, (uint32_t)(vicii.ram_base_phi2 - mem_ram)) < 0
230 /* VBank */
231 || SMW_W(m, (uint16_t)vicii.vbank_phi2) < 0) {
232 goto fail;
233 }
234
235 return snapshot_module_close(m);
236
237 fail:
238 if (m != NULL) {
239 snapshot_module_close(m);
240 }
241
242 return -1;
243 }
244
vicii_snapshot_read_module(snapshot_t * s)245 int vicii_snapshot_read_module(snapshot_t *s)
246 {
247 uint8_t major_version, minor_version;
248 int i;
249 snapshot_module_t *m;
250
251 m = snapshot_module_open(s, snap_module_name,
252 &major_version, &minor_version);
253 if (m == NULL) {
254 return -1;
255 }
256
257 if (snapshot_version_is_bigger(major_version, minor_version, SNAP_MAJOR, SNAP_MINOR)) {
258 log_error(vicii.log,
259 "Snapshot module version (%d.%d) newer than %d.%d.",
260 major_version, minor_version,
261 SNAP_MAJOR, SNAP_MINOR);
262 goto fail;
263 }
264
265 /* FIXME: initialize changes? */
266
267 if (0
268 /* AllowBadLines */
269 || SMR_B_INT(m, &vicii.allow_bad_lines) < 0
270 /* BadLine */
271 || SMR_B_INT(m, &vicii.bad_line) < 0
272 /* Blank */
273 || SMR_B_INT(m, &vicii.raster.blank_enabled) < 0
274 /* ColorBuf */
275 || SMR_BA(m, vicii.cbuf, 40) < 0
276 /* IdleState */
277 || SMR_B_INT(m, &vicii.idle_state) < 0
278 /* LPTrigger */
279 || SMR_B_INT(m, &vicii.light_pen.triggered) < 0
280 /* LPX */
281 || SMR_B_INT(m, &vicii.light_pen.x) < 0
282 /* LPY */
283 || SMR_B_INT(m, &vicii.light_pen.y) < 0
284 /* MatrixBuf */
285 || SMR_BA(m, vicii.vbuf, 40) < 0
286 /* NewSpriteDmaMask */
287 || SMR_B(m, &vicii.raster.sprite_status->new_dma_msk) < 0) {
288 goto fail;
289 }
290
291 {
292 uint32_t RamBase;
293
294 if (SMR_DW(m, &RamBase) < 0) {
295 goto fail;
296 }
297 vicii.ram_base_phi1 = mem_ram + RamBase;
298 }
299
300 /* Read the current raster line and the current raster cycle. As they
301 are a function of `clk', this is just a sanity check. */
302 {
303 uint16_t RasterLine;
304 uint8_t RasterCycle;
305
306 if (SMR_B(m, &RasterCycle) < 0
307 || SMR_W(m, &RasterLine) < 0) {
308 goto fail;
309 }
310
311 if (RasterCycle != (uint8_t)VICII_RASTER_CYCLE(maincpu_clk)) {
312 log_error(vicii.log,
313 "Not matching raster cycle (%d) in snapshot; should be %u.",
314 RasterCycle, VICII_RASTER_CYCLE(maincpu_clk));
315 goto fail;
316 }
317
318 if (RasterLine != (uint16_t)VICII_RASTER_Y(maincpu_clk)) {
319 log_error(vicii.log,
320 "VIC-II: Not matching raster line (%d) in snapshot; should be %u.",
321 RasterLine, VICII_RASTER_Y(maincpu_clk));
322 goto fail;
323 }
324 }
325
326 for (i = 0; i < 0x50; i++) {
327 if (SMR_B(m, &vicii.regs[i]) < 0 /* Registers */) {
328 goto fail;
329 }
330 }
331
332 if (0
333 /* DTV stuff */
334 || SMR_DW_INT(m, &vicii.counta) < 0
335 || SMR_DW_INT(m, &vicii.counta_mod) < 0
336 || SMR_DW_INT(m, &vicii.counta_step) < 0
337 || SMR_DW_INT(m, &vicii.countb) < 0
338 || SMR_DW_INT(m, &vicii.countb_mod) < 0
339 || SMR_DW_INT(m, &vicii.countb_step) < 0
340 || SMR_DW_INT(m, (int *)&vicii.extended_enable) < 0
341 || SMR_DW_INT(m, (int *)&vicii.extended_lockout) < 0
342 || SMR_DW_INT(m, (int *)&vicii.badline_disable) < 0
343 || SMR_DW_INT(m, (int *)&vicii.colorfetch_disable) < 0
344 || SMR_DW_INT(m, (int *)&vicii.overscan) < 0
345 || SMR_DW_INT(m, (int *)&vicii.high_color) < 0
346 || SMR_DW_INT(m, (int *)&vicii.border_off) < 0
347 || SMR_DW_INT(m, &vicii.raster_irq_offset) < 0
348 || SMR_DW_INT(m, &vicii.raster_irq_prevent) < 0
349 || SMR_BA(m, vicii.dtvpalette, 256) < 0
350 /* SbCollMask */
351 || SMR_B(m, &vicii.sprite_background_collisions) < 0
352 /* SpriteDmaMask */
353 || SMR_B(m, &vicii.raster.sprite_status->dma_msk) < 0
354 /* SsCollMask */
355 || SMR_B(m, &vicii.sprite_sprite_collisions) < 0
356 /* VBank */
357 || SMR_W_INT(m, &vicii.vbank_phi1) < 0
358 /* Vc */
359 || SMR_W_INT(m, &vicii.mem_counter) < 0
360 /* VcInc */
361 || SMR_B_INT(m, &vicii.mem_counter_inc) < 0
362 /* VcBase */
363 || SMR_W_INT(m, &vicii.memptr) < 0
364 /* VideoInt */
365 || SMR_B_INT(m, &vicii.irq_status) < 0) {
366 goto fail;
367 }
368
369 for (i = 0; i < 8; i++) {
370 if (0
371 /* SpriteXMemPtr */
372 || SMR_B_INT(m, &vicii.raster.sprite_status->sprites[i].memptr) < 0
373 /* SpriteXMemPtrInc */
374 || SMR_B_INT(m, &vicii.raster.sprite_status->sprites[i].memptr_inc) < 0
375 /* SpriteXExpFlipFlop */
376 || SMR_B_INT(m, &vicii.raster.sprite_status->sprites[i].exp_flag) < 0
377 ) {
378 goto fail;
379 }
380 }
381
382 /* FIXME: Recalculate alarms and derived values. */
383 #if 1
384 if (vicii.raster_irq_prevent) {
385 vicii.raster_irq_clk = CLOCK_MAX;
386 alarm_unset(vicii.raster_irq_alarm);
387 } else {
388 /*
389 We cannot use vicii_irq_set_raster_line as this would delay
390 an alarm on line 0 for one frame
391 */
392 unsigned int line = vicii.regs[0x12] | ((vicii.regs[0x11] & 0x80) << 1);
393
394 if (line < (unsigned int)vicii.screen_height) {
395 vicii.raster_irq_clk = (VICII_LINE_START_CLK(maincpu_clk)
396 + VICII_RASTER_IRQ_DELAY - INTERRUPT_DELAY
397 + (vicii.cycles_per_line * line));
398 vicii.raster_irq_clk += vicii.raster_irq_offset;
399 /* Raster interrupts on line 0 are delayed by 1 cycle. */
400 if (line == 0) {
401 vicii.raster_irq_clk++;
402 }
403
404 alarm_set(vicii.raster_irq_alarm, vicii.raster_irq_clk);
405 } else {
406 vicii.raster_irq_clk = CLOCK_MAX;
407 alarm_unset(vicii.raster_irq_alarm);
408 }
409 vicii.raster_irq_line = line;
410 }
411
412 #else
413 vicii_irq_set_raster_line(vicii.regs[0x12]
414 | ((vicii.regs[0x11] & 0x80) << 1));
415 #endif
416
417 /* compatibility with older versions */
418 vicii.ram_base_phi2 = vicii.ram_base_phi1;
419 vicii.vbank_phi2 = vicii.vbank_phi1;
420
421 vicii_update_memory_ptrs(VICII_RASTER_CYCLE(maincpu_clk));
422
423 /* Update sprite parameters. We had better do this manually, or the
424 VIC-II emulation could be quite upset. */
425 {
426 uint8_t msk;
427
428 for (i = 0, msk = 0x1; i < 8; i++, msk <<= 1) {
429 raster_sprite_t *sprite;
430 int tmp;
431
432 sprite = vicii.raster.sprite_status->sprites + i;
433
434 /* X/Y coordinates. */
435 tmp = vicii.regs[i * 2] + ((vicii.regs[0x10] & msk) ? 0x100 : 0);
436
437 /* (-0xffff makes sure it's updated NOW.) */
438 vicii_sprites_set_x_position(i, tmp, -0xffff);
439
440 sprite->y = (int)vicii.regs[i * 2 + 1];
441 sprite->x_expanded = (int)(vicii.regs[0x1d] & msk);
442 sprite->y_expanded = (int)(vicii.regs[0x17] & msk);
443 sprite->multicolor = (int)(vicii.regs[0x1c] & msk);
444 sprite->in_background = (int)(vicii.regs[0x1b] & msk);
445 sprite->color = (int) vicii.dtvpalette[vicii.regs[0x27 + i] & 0xf];
446 sprite->dma_flag = (int)(vicii.raster.sprite_status->new_dma_msk & msk);
447 }
448 }
449
450 vicii.sprite_fetch_msk = vicii.raster.sprite_status->new_dma_msk;
451 vicii.sprite_fetch_clk = VICII_LINE_START_CLK(maincpu_clk)
452 + vicii.sprite_fetch_cycle
453 - vicii.cycles_per_line;
454
455 /* calculate the sprite_fetch_idx */
456 {
457 const vicii_sprites_fetch_t *sf;
458
459 sf = vicii_sprites_fetch_table[vicii.sprite_fetch_msk];
460 i = 0;
461 while (sf[i].cycle >= 0 && sf[i].cycle + vicii.sprite_fetch_cycle <= vicii.cycles_per_line)
462 {
463 i++;
464 }
465 vicii.sprite_fetch_idx = i;
466 }
467
468 vicii.raster.xsmooth = vicii.regs[0x16] & 0x7;
469 vicii.raster.sprite_xsmooth = vicii.regs[0x16] & 0x7;
470 vicii.raster.ysmooth = vicii.regs[0x11] & 0x7;
471 vicii.raster.current_line = VICII_RASTER_Y(maincpu_clk); /* FIXME? */
472
473 vicii.raster.sprite_status->visible_msk = vicii.regs[0x15];
474
475 /* Update colors. */
476 vicii.raster.border_color = vicii.dtvpalette[vicii.regs[0x20]];
477 vicii.raster.background_color = vicii.dtvpalette[vicii.regs[0x21]];
478 vicii.ext_background_color[0] = vicii.dtvpalette[vicii.regs[0x22]];
479 vicii.ext_background_color[1] = vicii.dtvpalette[vicii.regs[0x23]];
480 vicii.ext_background_color[2] = vicii.dtvpalette[vicii.regs[0x24]];
481 vicii.raster.sprite_status->mc_sprite_color_1 = vicii.dtvpalette[vicii.regs[0x25] & 0xf];
482 vicii.raster.sprite_status->mc_sprite_color_2 = vicii.dtvpalette[vicii.regs[0x26] & 0xf];
483
484 vicii.raster.blank = !(vicii.regs[0x11] & 0x10);
485
486 if (VICII_IS_ILLEGAL_MODE(vicii.raster.video_mode)) {
487 vicii.raster.idle_background_color = 0;
488 vicii.force_black_overscan_background_color = 1;
489 } else {
490 vicii.raster.idle_background_color
491 = vicii.raster.background_color;
492 vicii.force_black_overscan_background_color = 0;
493 }
494
495 if (vicii.regs[0x11] & 0x8) {
496 vicii.raster.display_ystart = vicii.row_25_start_line;
497 vicii.raster.display_ystop = vicii.row_25_stop_line;
498 } else {
499 vicii.raster.display_ystart = vicii.row_24_start_line;
500 vicii.raster.display_ystop = vicii.row_24_stop_line;
501 }
502
503 if (vicii.regs[0x16] & 0x8) {
504 vicii.raster.display_xstart = VICII_40COL_START_PIXEL;
505 vicii.raster.display_xstop = VICII_40COL_STOP_PIXEL;
506 } else {
507 vicii.raster.display_xstart = VICII_38COL_START_PIXEL;
508 vicii.raster.display_xstop = VICII_38COL_STOP_PIXEL;
509 }
510
511 /* `vicii.raster.draw_idle_state', `vicii.raster.open_right_border' and
512 `vicii.raster.open_left_border' should be needed, but they would only
513 affect the current vicii.raster line, and would not cause any
514 difference in timing. So who cares. */
515
516 /* FIXME: `vicii.ycounter_reset_checked'? */
517 /* FIXME: `vicii.force_display_state'? */
518
519 vicii.memory_fetch_done = 0; /* FIXME? */
520
521 vicii_update_video_mode(VICII_RASTER_CYCLE(maincpu_clk));
522
523 vicii_store(0x3c, (uint8_t)vicii.regs[0x3c]);
524
525 vicii.draw_clk = maincpu_clk + (vicii.draw_cycle - VICII_RASTER_CYCLE(maincpu_clk));
526 vicii.last_emulate_line_clk = vicii.draw_clk - vicii.cycles_per_line;
527 alarm_set(vicii.raster_draw_alarm, vicii.draw_clk);
528
529 {
530 uint32_t dw;
531 uint8_t b;
532
533 if (0
534 || SMR_DW(m, &dw) < 0 /* FetchEventTick */
535 || SMR_B(m, &b) < 0 /* FetchEventType */
536 ) {
537 goto fail;
538 }
539
540 vicii.fetch_clk = maincpu_clk + dw;
541 vicii.fetch_idx = b;
542
543 alarm_set(vicii.raster_fetch_alarm, vicii.fetch_clk);
544 }
545
546 if (vicii.irq_status & 0x80) {
547 interrupt_restore_irq(maincpu_int_status, vicii.int_num, 1);
548 }
549
550 /* added in version 1.1 of snapshot format */
551 if (minor_version > 0) {
552 uint32_t RamBase;
553
554 if (0
555 || SMR_DW(m, &RamBase) < 0
556 || SMR_W_INT(m, &vicii.vbank_phi2) < 0 /* VBank */
557 ) {
558 goto fail;
559 }
560 vicii.ram_base_phi2 = mem_ram + RamBase;
561
562 vicii_update_memory_ptrs(VICII_RASTER_CYCLE(maincpu_clk));
563 }
564
565 raster_force_repaint(&vicii.raster);
566 snapshot_module_close(m);
567 return 0;
568
569 fail:
570 if (m != NULL) {
571 snapshot_module_close(m);
572 }
573 return -1;
574 }
575