OSDN Git Service

workram mode での RAM 取得方法が変なので修正
[unagi/old-svn-converted.git] / client / trunk / anago / script_dump.c
1 #include <assert.h>
2 #include <stdio.h>
3 #include <string.h>
4 #include <squirrel.h>
5 #include <sqstdio.h>
6 #include <sqstdaux.h>
7 #include "type.h"
8 #include "file.h"
9 #include "widget.h"
10 #include "romimage.h"
11 #include "memory_manage.h"
12 #include "reader_master.h"
13 #include "squirrel_wrap.h"
14 #include "script_common.h"
15 #include "script_dump.h"
16
17 #define USERPOINTER_GET(Pointer, Result) \
18  struct dump_config *Pointer; \
19  SQRESULT Result =  qr_userpointer_get(v, (SQUserPointer) &Pointer); \
20  if(SQ_FAILED(Result)){ \
21         return Result; \
22  }
23
24
25 static SQInteger cpu_write(HSQUIRRELVM v)
26 {
27         USERPOINTER_GET(d, r)
28         cpu_write_execute(v, d->handle, d->cpu.access);
29         return 0;
30 }
31
32 //ここの printf は debug 用に残しておく
33 static void buffer_show(struct memory *t, long length)
34 {
35         int i;
36         const uint8_t *buf = t->data + t->offset;
37 #ifdef _UNICODE
38         wprintf(L"%s 0x%06x:", t->name, t->offset);
39 #else
40         printf("%s 0x%06x:", t->name, t->offset);
41 #endif
42         for(i = 0; i < 0x10; i++){
43                 wgChar dump[3+1];
44 #ifdef _UNICODE
45                 //wsprintf(dump, L"%02x", buf[i]);
46 #else
47                 sprintf(dump, "%02x", buf[i]);
48 #endif
49                 switch(i){
50                 case 7:
51                         dump[2] = wgT('-');
52                         break;
53                 case 0x0f:
54                         dump[2] = wgT('\0');
55                         break;
56                 default:
57                         dump[2] = wgT(' ');
58                         break;
59                 }
60                 dump[3] = wgT('\0');
61 #ifdef _UNICODE
62                 wprintf(L"%s", dump);
63 #else
64                 printf("%s", dump);
65 #endif
66         }
67         int sum = 0;
68         while(length != 0){
69                 sum += (int) *buf;
70                 buf++;
71                 length--;
72         }
73 #ifdef _UNICODE
74         wprintf(L":0x%06x\n", sum);
75 #else
76         printf(":0x%06x\n", sum);
77 #endif
78         fflush(stdout);
79 }
80
81 static SQInteger read_memory(HSQUIRRELVM v, const struct reader_handle *h, struct dump_memory_driver *t, bool progress)
82 {
83         long address, length;
84         SQRESULT r = qr_argument_get(v, 2, &address, &length);
85         if(SQ_FAILED(r)){
86                 return r;
87         }
88         assert(t->memory.attribute == MEMORY_ATTR_WRITE);
89         t->access->memory_read(h, &t->gauge, address, length == 0 ? 1: length, t->memory.data + t->memory.offset);
90         if((length != 0) && (progress == false)){
91                 buffer_show(&t->memory, length);
92         }
93         t->memory.offset += length;
94         return 0;
95 }
96
97 static SQInteger cpu_read(HSQUIRRELVM v)
98 {
99         USERPOINTER_GET(d, r)
100         r = read_memory(v, d->handle, &d->cpu, d->progress);
101         return r;
102 }
103
104 static SQInteger ppu_read(HSQUIRRELVM v)
105 {
106         USERPOINTER_GET(d, r)
107         r = read_memory(v, d->handle, &d->ppu, d->progress);
108         return r;
109 }
110
111 static SQInteger ppu_ramfind(HSQUIRRELVM v)
112 {
113         struct dump_config *d;
114         enum{
115                 testsize = 8,
116                 testaddress = 1234
117         };
118         static const uint8_t test_val[testsize] = {0xaa, 0x55, 0, 0xff, 0x46, 0x49, 0x07, 0x21};
119         static const uint8_t test_str[testsize] = "pputest";
120         uint8_t test_result[testsize];
121         SQRESULT r =  qr_userpointer_get(v, (SQUserPointer) &d);
122         struct dump_memory_driver *p = &d->ppu;
123
124         if(SQ_FAILED(r)){
125                 return r;
126         }
127         p->access->memory_write(d->handle, testaddress, testsize, test_val);
128         p->access->memory_read(d->handle, &GAUGE_DUMMY, testaddress, testsize, test_result);
129         if(memcmp(test_val, test_result, testsize) != 0){
130                 sq_pushbool(v, SQFalse);
131                 return 1;
132         }
133         p->access->memory_write(d->handle, testaddress, testsize, test_str);
134         p->access->memory_read(d->handle, &GAUGE_DUMMY, testaddress, testsize, test_result);
135         if(memcmp(test_str, test_result, testsize) != 0){
136                 sq_pushbool(v, SQFalse);
137                 return 1;
138         }
139         p->memory.offset = 0;
140         p->memory.size = 0;
141         sq_pushbool(v, SQTrue);
142         return 1;
143 }
144
145 static SQInteger return_true(HSQUIRRELVM v)
146 {
147         sq_pushbool(v, SQFalse);
148         return 1;
149 }
150
151 static void memory_new_init(struct dump_memory_driver *d)
152 {
153         d->memory.offset = 0;
154         d->memory.data = Malloc(d->memory.size);
155         d->gauge.range_set(d->gauge.bar, d->memory.size);
156         d->gauge.value_set(d->gauge.bar, d->gauge.label, 0);
157 }
158
159 //test 時/1度目の call で使用
160 static SQInteger memory_new(HSQUIRRELVM v)
161 {
162         USERPOINTER_GET(d, r)
163
164         r = qr_argument_get(v, 2, &d->cpu.memory.size, &d->ppu.memory.size);
165         if(SQ_FAILED(r)){
166                 return r;
167         }
168
169         memory_new_init(&d->cpu);
170         if(d->mode == MODE_ROM_DUMP){
171                 memory_new_init(&d->ppu);
172         }
173         return 0;
174 }
175
176 //dump 時/2度目の call で nesfile_save として使用
177 static SQInteger nesfile_save(HSQUIRRELVM v)
178 {
179         USERPOINTER_GET(d, r)
180
181         struct romimage image;
182         long mirrorfind;
183         r = qr_argument_get(v, 2, &image.mappernum, &mirrorfind);
184         if(SQ_FAILED(r)){
185                 return r;
186         }
187         image.cpu_rom = d->cpu.memory;
188         image.cpu_ram.data = NULL;
189         image.ppu_rom = d->ppu.memory;
190         image.mirror = MIRROR_PROGRAMABLE;
191         if(mirrorfind == 1){
192                 uint8_t c = d->control->vram_connection(d->handle);
193                 if(DEBUG == 1){
194                         d->log.append(d->log.object, wgT("vram connection %x\n"), c);
195                 }
196 /*
197 kazzo 1.0 return value H:9 V:5
198 kazzo 2.x return value H:C V:A
199 */
200                 if(c == 0x05 || c == 0x0a){
201                         image.mirror = MIRROR_VERTICAL;
202                 }else{
203                         image.mirror = MIRROR_HORIZONAL;
204                 }
205         }
206         image.backupram = 0;
207         if(d->battery == true){
208                 image.backupram = 1;
209         }
210         d->crc = nesfile_create(&d->log, &image, d->target);
211         nesbuffer_free(&image, 0); //0 is MODE_xxx_xxxx
212         
213         d->cpu.memory.data = NULL;
214         d->ppu.memory.data = NULL;
215         return 0;
216 }
217
218 static bool length_check_core(struct dump_config *d, struct dump_memory_driver *m, const wgChar * str)
219 {
220         bool ret = true;
221         if((m->read_count_bit & 0x7) != 0){
222                 ret = false;
223         }
224         m->read_count_byte += m->read_count_bit >> 3;
225         if(m->memory.size != m->read_count_byte){
226                 ret = false;
227         }
228         if(ret == false){
229                 d->log.append(d->log.object, wgT("%s is not connected 0x%06x.%d/0x%06x\n"), str, (int) m->read_count_byte, (int) m->read_count_bit & 7, (int) m->memory.size);
230         }
231         
232         return ret;
233 }
234
235 //dump 時/1度目の call で nesfile_save として使用
236 static SQInteger length_check(HSQUIRRELVM v)
237 {
238         USERPOINTER_GET(d, r)
239
240         bool cpu = true, ppu = true;
241         r = 0;
242         cpu = length_check_core(d, &d->cpu, d->mode == MODE_ROM_DUMP ? wgT("board.cpu_rom.size") : wgT("board.cpu_ram.size"));
243         ppu = length_check_core(d, &d->ppu, wgT("board.ppu_rom.size"));
244         if(cpu == false || ppu == false){
245                 r = sq_throwerror(v, wgT("script logical error"));
246         }
247         return r;
248 }
249
250 static SQInteger read_count(HSQUIRRELVM v, const struct textcontrol *l, struct dump_memory_driver *t, const struct range *range_address, const struct range *range_length)
251 {
252         long address, length;
253         SQRESULT r = qr_argument_get(v, 2, &address, &length);
254         if(SQ_FAILED(r)){
255                 return r;
256         }
257         r = range_check(v, wgT("length"), length, range_length);
258         if(SQ_FAILED(r)){
259                 return r;
260         }
261         if((address < range_address->start) || ((address + length) > range_address->end)){
262                 l->append(l->object, wgT("address range must be 0x%06x to 0x%06x"), (int) range_address->start, (int) range_address->end);
263                 return sq_throwerror(v, wgT("script logical error"));;
264         }
265         t->read_count_byte += length;
266         return 0;
267 }
268 static SQInteger cpu_read_count(HSQUIRRELVM v)
269 {
270         static const struct range range_address = {0x8000, 0x10000};
271         //length == 0 は 対象アドレスを呼んで、バッファにいれない。mmc2, mmc4 で使用する。
272         static const struct range range_length = {0x0000, 0x4000};
273         USERPOINTER_GET(d, r)
274
275         return read_count(v, &d->log, &d->cpu, &range_address, &range_length);
276 }
277
278 static SQInteger ppu_read_count(HSQUIRRELVM v)
279 {
280         static const struct range range_address = {0x0000, 0x2000};
281         static const struct range range_length = {0x0001, 0x2000};
282         USERPOINTER_GET(d, r)
283
284         return read_count(v, &d->log, &d->ppu, &range_address, &range_length);
285 }
286
287 static SQInteger cpu_read_register_check(HSQUIRRELVM v)
288 {
289         static const struct range range_address = {0x4800, 0x7fff};
290         static const struct range range_byte = {0, 0xff};
291         USERPOINTER_GET(d, r)
292
293         long address, byte;
294         r = qr_argument_get(v, 2, &address, &byte);
295         if(SQ_FAILED(r)){
296                 return r;
297         }
298         r = range_check(v, wgT("address"), address, &range_address);
299         if(SQ_FAILED(r)){
300                 return r;
301         }
302         r = range_check(v, wgT("byte"), byte, &range_byte);
303         if(SQ_FAILED(r)){
304                 return r;
305         }
306
307         sq_pushinteger(v, byte);
308         return 1;
309 }
310
311 static SQInteger cpu_read_register(HSQUIRRELVM v)
312 {
313         USERPOINTER_GET(d, r)
314         long address, dummy;
315         r = qr_argument_get(v, 2, &address, &dummy);
316         if(SQ_FAILED(r)){
317                 return r;
318         }
319
320         uint8_t readdata;
321         d->cpu.access->memory_read(d->handle, &GAUGE_DUMMY, address, 1, &readdata);
322
323         sq_pushinteger(v, readdata);
324         return 1;
325 }
326
327 static SQInteger memory_size_set(HSQUIRRELVM v)
328 {
329         USERPOINTER_GET(d, r)
330         r = qr_argument_get(v, 2, &d->cpu.memory.size, &d->ppu.memory.size);
331         return r;
332 }
333
334 static bool script_execute(HSQUIRRELVM v, struct dump_config *d)
335 {
336         bool ret = true;
337         if(SQ_FAILED(sqstd_dofile(v, wgT("dumpcore.nut"), SQFalse, SQTrue))){
338                 d->log.append(d->log.object, wgT("dump core script error\n"));
339                 ret = false;
340         }else{
341                 SQRESULT r = qr_call(
342                         v, wgT("dump"), (SQUserPointer) d, d->script, 
343                         3, d->mappernum, d->cpu.increase, d->ppu.increase
344                 );
345                 if(SQ_FAILED(r)){
346                         ret = false;
347                         Free(d->cpu.memory.data);
348                         Free(d->ppu.memory.data);
349                         d->cpu.memory.data = NULL;
350                         d->ppu.memory.data = NULL;
351                 }
352         }
353         return ret;
354 }
355
356 static void dump_memory_driver_init(struct dump_memory_driver *dd, enum memory_attribute at)
357 {
358         dd->memory.size = 0;
359         dd->memory.offset = 0;
360         dd->memory.attribute = at;
361         dd->memory.transtype = TRANSTYPE_FULL;
362         dd->memory.data = NULL;
363         dd->read_count_byte = 0;
364         dd->read_count_bit = 0;
365 }
366
367 bool script_dump_execute(struct dump_config *d)
368 {
369         dump_memory_driver_init(&d->cpu, MEMORY_ATTR_WRITE);
370         d->cpu.memory.name = wgT("Program");
371         
372         dump_memory_driver_init(&d->ppu, MEMORY_ATTR_WRITE);
373         d->ppu.memory.name = wgT("Charcter");
374         
375         {
376                 HSQUIRRELVM v = qr_open(&d->log);
377                 qr_function_register_global(v, wgT("cpu_write"), cpu_write_check);
378                 qr_function_register_global(v, wgT("memory_new"), memory_size_set);
379                 qr_function_register_global(v, wgT("nesfile_save"), length_check);
380                 qr_function_register_global(v, wgT("cpu_read"), cpu_read_count);
381                 qr_function_register_global(v, wgT("ppu_read"), ppu_read_count);
382                 qr_function_register_global(v, wgT("ppu_ramfind"), return_true);
383                 if(script_execute(v, d) == false){
384                         qr_close(v);
385                         return false;
386                 }
387                 qr_close(v);
388         }
389
390         d->handle = d->control->open(d->except, &d->log);
391         if(d->handle == NULL){
392                 d->log.append(d->log.object, wgT("reader open error\n"));
393                 return false;
394         }
395 /*      d->control->init(d->handle);
396         if(connection_check(d->handle, &d->log, d->cpu.access, d->ppu.access) == false){
397                 d->control->close(d->handle);
398                 return false;
399         }*/
400         {
401                 HSQUIRRELVM v = qr_open(&d->log); 
402                 qr_function_register_global(v, wgT("memory_new"), memory_new);
403                 qr_function_register_global(v, wgT("nesfile_save"), nesfile_save);
404                 qr_function_register_global(v, wgT("cpu_write"), cpu_write);
405                 qr_function_register_global(v, wgT("cpu_read"), cpu_read);
406                 qr_function_register_global(v, wgT("ppu_read"), ppu_read);
407                 qr_function_register_global(v, wgT("ppu_ramfind"), ppu_ramfind);
408                 script_execute(v, d);
409                 qr_close(v);
410         }
411         d->control->close(d->handle);
412         d->handle = NULL;
413         return true;
414 }
415
416 static bool workram_execute(HSQUIRRELVM v, struct dump_config *d)
417 {
418         bool ret = true;
419         if(SQ_FAILED(sqstd_dofile(v, wgT("dumpcore.nut"), SQFalse, SQTrue))){
420                 d->log.append(d->log.object, wgT("dump core script error\n"));
421                 ret = false;
422         }else{
423                 SQRESULT r = qr_call(
424                         v, wgT("workram_rw"), (SQUserPointer) d, d->script, 
425                         1, d->cpu.increase
426                 );
427                 if(SQ_FAILED(r)){
428                         ret = false;
429                         Free(d->cpu.memory.data);
430                         d->cpu.memory.data = NULL;
431 //                      Free(d->ppu.memory.data);
432 //                      d->ppu.memory.data = NULL;
433                 }
434         }
435         return ret;
436 }
437
438 static SQInteger cpu_ramrw_check(HSQUIRRELVM v)
439 {
440         static const struct range range_address = {0x4800, 0xdfff};
441         static const struct range range_length = {1, 0x2000};
442         USERPOINTER_GET(d, r)
443
444         return read_count(v, &d->log, &d->cpu, &range_address, &range_length);
445 }
446
447 static SQInteger ramimage_open(HSQUIRRELVM v)
448 {
449         USERPOINTER_GET(d, r)
450         memory_new(v);
451         if(buf_load(d->cpu.memory.data, d->target, d->cpu.memory.size) == NG){
452                 return r = sq_throwerror(v, wgT("RAM image open error"));
453         }
454         return 0;
455 }
456
457 static SQInteger memory_finalize(HSQUIRRELVM v)
458 {
459         USERPOINTER_GET(d, r)
460
461         if(d->mode == MODE_RAM_READ){
462                 buf_save(d->cpu.memory.data, d->target, d->cpu.memory.size);
463         }
464         Free(d->cpu.memory.data);
465         d->cpu.memory.data = NULL;
466 //      Free(d->ppu.memory.data);
467 //      d->ppu.memory.data = NULL;
468         
469         return 0;
470 }
471
472 static SQInteger cpu_write_ramimage(HSQUIRRELVM v)
473 {
474         USERPOINTER_GET(d, r)
475
476         long address, length;
477         uint8_t *cmp;
478         const uint8_t *writedata = d->cpu.memory.data;
479         writedata += d->cpu.memory.offset;
480         
481         r = qr_argument_get(v, 2, &address, &length);
482         if(SQ_FAILED(r)){
483                 return r;
484         }
485         cmp = Malloc(length);
486         assert(d->cpu.memory.attribute == MEMORY_ATTR_READ);
487
488         d->cpu.access->memory_write(
489                 d->handle, address, length, writedata
490         );
491         d->cpu.access->memory_read(
492                 d->handle, &d->cpu.gauge, address, length, cmp
493         );
494         d->cpu.memory.offset += length;
495
496         r = memcmp(cmp, writedata, length);
497         Free(cmp);
498         if(r != 0){
499                 r = sq_throwerror(v, wgT("memory write failed"));
500         }
501         return 0;
502 }
503
504 static SQInteger mode_is_read(HSQUIRRELVM v)
505 {
506         USERPOINTER_GET(d, r)
507         sq_pushbool(v, d->mode == MODE_RAM_READ ? SQTrue : SQFalse);
508         return 1;
509 }
510
511 static SQInteger cpu_read_bit_check(HSQUIRRELVM v)
512 {
513         static const struct range range_address = {0x4800, 0xdfff};
514         static const struct range range_bit = {0, 7};
515         USERPOINTER_GET(d, r)
516
517         long address, bit;
518         r = qr_argument_get(v, 2, &address, &bit);
519         if(SQ_FAILED(r)){
520                 return r;
521         }
522         r = range_check(v, wgT("address"), address, &range_address);
523         if(SQ_FAILED(r)){
524                 return r;
525         }
526         r = range_check(v, wgT("bit"), bit, &range_bit);
527         if(SQ_FAILED(r)){
528                 return r;
529         }
530         d->cpu.read_count_bit += 1;
531         return 0;
532 }
533
534 static inline void gauge_increment(const struct gauge *g)
535 {
536         g->value_add(g->bar, g->label, 1);
537 }
538
539 static SQInteger cpu_read_bit_msb(HSQUIRRELVM v)
540 {
541         USERPOINTER_GET(d, r)
542         long address, bit;
543         r = qr_argument_get(v, 2, &address, &bit);
544         if(SQ_FAILED(r)){
545                 return r;
546         }
547
548         assert(d->cpu.memory.attribute == MEMORY_ATTR_WRITE);
549         uint8_t readdata;
550
551         d->cpu.access->memory_read(d->handle, &GAUGE_DUMMY, address, 1, &readdata);
552         readdata >>= bit;
553         readdata &= 1;
554
555         if(d->cpu.read_count_bit == 0){
556                 d->cpu.bitbuffer = 0;
557         }
558         d->cpu.bitbuffer |= readdata;
559
560         d->cpu.read_count_bit += 1;
561         
562         if(d->cpu.read_count_bit == 8){
563                 d->cpu.read_count_bit = 0;
564                 d->cpu.memory.data[d->cpu.memory.offset] = d->cpu.bitbuffer;
565                 d->cpu.memory.offset += 1;
566                 gauge_increment(&d->cpu.gauge);
567         }else{
568                 d->cpu.bitbuffer <<= 1;
569         }
570         return 0;
571 }
572
573 static SQInteger cpu_fetch_bit_check(HSQUIRRELVM v)
574 {
575         USERPOINTER_GET(d, r)
576         d->cpu.read_count_bit += 1;
577         return 0;
578 }
579
580 static SQInteger cpu_fetch_bit_msb(HSQUIRRELVM v)
581 {
582         USERPOINTER_GET(d, r)
583
584         if(d->cpu.read_count_bit == 0){
585                 d->cpu.bitbuffer = d->cpu.memory.data[d->cpu.memory.offset];
586         }
587         sq_pushinteger(v, d->cpu.bitbuffer & 0x80);
588         
589         d->cpu.read_count_bit += 1;
590         if(d->cpu.read_count_bit == 8){
591                 d->cpu.read_count_bit = 0;
592                 d->cpu.memory.offset += 1;
593                 gauge_increment(&d->cpu.gauge);
594         }else{
595                 d->cpu.bitbuffer <<= 1;
596         }
597         return 1;
598 }
599
600 bool script_workram_execute(struct dump_config *d)
601 {
602         dump_memory_driver_init(&d->cpu, d->mode == MODE_RAM_READ ? MEMORY_ATTR_WRITE : MEMORY_ATTR_READ);
603         dump_memory_driver_init(&d->ppu, MEMORY_ATTR_NOTUSE);
604         d->cpu.memory.name = wgT("Workram");
605         d->ppu.memory.name = wgT("N/A");
606
607         {
608                 HSQUIRRELVM v = qr_open(&d->log);
609                 qr_function_register_global(v, wgT("memory_new"), memory_size_set);
610                 qr_function_register_global(v, wgT("cpu_write"), cpu_write_check);
611                 qr_function_register_global(v, wgT("cpu_ramrw"), cpu_ramrw_check);
612                 qr_function_register_global(v, wgT("memory_finalize"), length_check);
613                 qr_function_register_global(v, wgT("mode_is_read"), mode_is_read);
614                 qr_function_register_global(v, wgT("cpu_read_register"), cpu_read_register_check);
615                 switch(d->mode){
616                 case MODE_RAM_READ:
617                         qr_function_register_global(v, wgT("cpu_read_bit_msb"), cpu_read_bit_check);
618                         break;
619                 case MODE_RAM_WRITE:
620                         qr_function_register_global(v, wgT("cpu_fetch_bit_msb"), cpu_fetch_bit_check);
621                         break;
622                 default:
623                         break;
624                 }
625                 if(workram_execute(v, d) == false){
626                         qr_close(v);
627                         return false;
628                 }
629                 qr_close(v);
630         }
631         
632         d->handle = d->control->open(d->except, &d->log);
633         if(d->handle == NULL){
634                 d->log.append(d->log.object, wgT("reader open error\n"));
635                 return false;
636         }
637         assert((d->cpu.read_count_bit & 7) == 0);
638         d->cpu.read_count_bit = 0;
639         d->control->init(d->handle);
640         if(connection_check(d->handle, &d->log, d->cpu.access, d->ppu.access) == false){
641                 d->control->close(d->handle);
642                 return false;
643         }
644         {
645                 HSQUIRRELVM v = qr_open(&d->log); 
646                 qr_function_register_global(v, wgT("cpu_write"), cpu_write);
647                 qr_function_register_global(v, wgT("cpu_read_register"), cpu_read_register);
648                 qr_function_register_global(v, wgT("mode_is_read"), mode_is_read);
649                 switch(d->mode){
650                 case MODE_RAM_READ:
651                         qr_function_register_global(v, wgT("memory_new"), memory_new);
652                         qr_function_register_global(v, wgT("cpu_ramrw"), cpu_read);
653                         qr_function_register_global(v, wgT("cpu_read_bit_msb"), cpu_read_bit_msb);
654                         break;
655                 case MODE_RAM_WRITE:
656                         qr_function_register_global(v, wgT("memory_new"), ramimage_open);
657                         qr_function_register_global(v, wgT("cpu_ramrw"), cpu_write_ramimage);
658                         qr_function_register_global(v, wgT("cpu_fetch_bit_msb"), cpu_fetch_bit_msb);
659                         break;
660                 default:
661                         assert(0);
662                         break;
663                 }
664                 qr_function_register_global(v, wgT("memory_finalize"), memory_finalize);
665                 workram_execute(v, d);
666                 qr_close(v);
667         }
668         d->control->close(d->handle);
669         d->handle = NULL;
670         return true;
671 }