1 // license:BSD-3-Clause
2 // copyright-holders:Antoine Mine
3 /**********************************************************************
4
5 Copyright (C) Antoine Mine' 2006
6
7 Thomson 8-bit computers
8
9 **********************************************************************/
10
11 #include "thom_cas.h"
12 #include "pool.h"
13
14 #include <cassert>
15 #include <cmath>
16
17
18 /***************************** configuration **************************/
19
20 #define K7_SPEED_HACK 0
21 /* When set to 1, bits extracted from k7 files are not transformed into a
22 raw wave. It saves some time & memory but disables the ability to "hear" the
23 cassette.
24 This does not affect raw wave files.
25 Also, we do not do this for k5 files because the corresponding wave
26 files are more compact anyway. */
27 /* It must be set accordingly in machine/thomson.c */
28
29 #define VERBOSE 0 /* 0, 1 or 2 */
30
31
32 /***************************** utilities **************************/
33
34 #define PRINT(x) printf x
35
36 #define LOG(x) do { if (VERBOSE > 0) printf x; } while (0)
37 #define VLOG(x) do { if (VERBOSE > 1) printf x; } while (0)
38
39
40 #define TO7_BIT_LENGTH 0.001114
41
42 #define TO7_FREQ_CASS_0 4500.
43 #define TO7_FREQ_CASS_1 6300.
44
45 #define TO7_PERIOD_CASS_0 ( 1. / TO7_FREQ_CASS_0 )
46 #define TO7_PERIOD_CASS_1 ( 1. / TO7_FREQ_CASS_1 )
47
48 static uint32_t to7_k7_bitsize;
49 static uint8_t* to7_k7_bits;
50
51 #define MO5_BIT_LENGTH 0.000833
52 #define MO5_HBIT_LENGTH (MO5_BIT_LENGTH / 2.)
53
54 /************************* k7 format *************************/
55
56 /* The k7 format is widespread among existing Thomson emulators.
57 It represents the stream of bytes returned by the BIOS cassette routine,
58 and so, is used by emulators that bypass the BIOS routine.
59 It is generally hacked from the raw image more or less by hand
60 (in particular, to by-pass special loaders and copy-protection schemes).
61
62 As we run the original BIOS routine, we need to hack back the k7 file
63 into the original stream.
64
65 In theory, the byte stream is a sequence of blocks of the form:
66 - 2-byte header: 01 3C
67 - 1-byte type
68 00 = start of file, the block contains the filename
69 01 = chunk of file data
70 FF = end of file
71 - 1-byte size
72 - size bytes of data
73 - one byte of CRC
74
75 These are separated by 0xff fillers.
76
77 There are also a variety of non-standard blocks, and we try to handle them.
78
79 Our main problem is to guess where the places where the motor will be
80 cut-off and introduce there sequences of 1 bit for synchronization.
81
82 Note: as a nice effect, if our routine succeeds, it constructs a wave
83 file that should be usable on a real computer.
84 */
85
86
87
88 static const cassette_image::Modulation to7_k7_modulation =
89 {
90 cassette_image::MODULATION_SQUAREWAVE,
91 4000.0, 4500.0, 5000.0,
92 5500.0, 6300.0, 7500.0
93 };
94
95
96
to7_k7_identify(cassette_image * cass,cassette_image::Options * opts)97 static cassette_image::error to7_k7_identify ( cassette_image *cass, cassette_image::Options *opts )
98 {
99 cassette_image::error e = cass->modulation_identify( to7_k7_modulation, opts );
100 return e;
101 }
102
103
104
to7_k7_load(cassette_image * cass)105 static cassette_image::error to7_k7_load( cassette_image *cass )
106 {
107 #if ! K7_SPEED_HACK
108 static const int8_t square_wave[] = { -128, 127 };
109 double time = 0.;
110 #endif
111 size_t size = cass->image_size( ), pos = 0;
112 int i, sz, sz2, bitmax = 1024, invalid = 0;
113 uint8_t in, typ, block[264];
114
115 LOG (( "to7_k7_load: start conversion, size=%li\n", (long)size ));
116 PRINT (( "to7_k7_load: open cassette, length: %li bytes\n", (long) size ));
117
118 if ( to7_k7_bits )
119 {
120 free( to7_k7_bits );
121 to7_k7_bits = nullptr;
122 }
123
124 to7_k7_bitsize = 0;
125 to7_k7_bits = (uint8_t*)malloc(bitmax );
126
127 /* store one period */
128 #if K7_SPEED_HACK
129 #define K7_PUT( PERIOD )
130 #else
131 #define K7_PUT( PERIOD ) \
132 do \
133 { \
134 cassette_image::error err; \
135 err = cass->put_samples( 0, time, (PERIOD), 2, 1, \
136 square_wave, cassette_image::WAVEFORM_8BIT ); \
137 if ( err != cassette_image::error::SUCCESS ) \
138 return err; \
139 time += (PERIOD); \
140 } while (0)
141 #endif
142
143 /* store one bit */
144 #define K7_PUT_BIT( BIT ) \
145 do \
146 { \
147 int b; \
148 if ( BIT ) \
149 { \
150 for ( b = 0; b < 7; b++ ) \
151 K7_PUT( TO7_PERIOD_CASS_1 ); \
152 } \
153 else \
154 { \
155 for ( b = 0; b < 5; b++ ) \
156 K7_PUT( TO7_PERIOD_CASS_0 ); \
157 } \
158 if ( to7_k7_bitsize + 1 >= bitmax ) \
159 { \
160 uint8_t* a = (uint8_t*)malloc(bitmax * 2); \
161 memcpy ( a, to7_k7_bits, bitmax ); \
162 bitmax *= 2; \
163 to7_k7_bits = a; \
164 } \
165 to7_k7_bits[ to7_k7_bitsize++ ] = (BIT); \
166 } while (0)
167
168 /* store one byte, with start / stop bits */
169 #define K7_PUT_BYTE( BYTE ) \
170 do \
171 { \
172 uint8_t x; \
173 K7_PUT_BIT( 0 ); \
174 for ( x = 0; x < 8; x++ ) \
175 K7_PUT_BIT( ( (BYTE) >> x ) & 1 ); \
176 K7_PUT_BIT( 1 ); \
177 K7_PUT_BIT( 1 ); \
178 } while (0)
179
180 #define K7_FILL_1( SIZE ) \
181 do \
182 { \
183 if ( (SIZE) > 0 ) { \
184 int ii; \
185 LOG (( "to7_k7_load: 1-filler size=%i bitstart=%i\n", \
186 (SIZE), to7_k7_bitsize )); \
187 for ( ii = 0; ii < (SIZE); ii++ ) K7_PUT_BIT( 1 ); \
188 } \
189 } while (0)
190
191 #define K7_FILL_ff( SIZE ) \
192 do \
193 { \
194 if ( (SIZE) > 0 ) \
195 { \
196 int ii; \
197 LOG (( "to7_k7_load: 0xff filler size=%i bitstart=%i\n", (SIZE), to7_k7_bitsize )); \
198 for ( ii = 0; ii < (SIZE); ii++ ) \
199 K7_PUT_BYTE( 0xff ); \
200 } \
201 } while (0)
202
203 /* check format */
204 cass->image_read( block, 0, 64 );
205 for ( i = 3; ; i++ )
206 {
207 if ( ( i >= size ) || ( i >= 64 ) )
208 {
209 /* ? */
210 PRINT (( "to7_k7_load: WARNING: this does not look like a MO or TO cassette.\n" ));
211 break;
212 }
213 else if ( ( block[i-3] == 0x01 ) && ( block[i-2] == 0x3c ) && ( block[i-1] == 0x5a ) && ! block[i] )
214 {
215 /* MO */
216 PRINT (( "to7_k7_load: WARNING: this looks like a MO cassette, not a TO one.\n" ));
217 break;
218 }
219 else if ( ( block[i-3] == 0xff ) && ( block[i-2] == 0x01 ) && ( block[i-1] == 0x3c ) && ! block[i] )
220 {
221 /* TO */
222 break;
223 }
224 }
225
226 /* skip to first 0xff filler */
227 for ( sz = 0; pos < size; pos++, sz++ )
228 {
229 cass->image_read( &in, pos, 1 );
230 if ( in == 0xff )
231 break;
232 }
233 if ( sz > 0 )
234 LOG (( "to7_k7_load: skip %i trash bytes\n", sz ));
235
236 /* loop over regular blocks */
237 while ( pos < size )
238 {
239 rebounce:
240 /* skip 0xff filler */
241 for ( sz = 0; pos < size; pos++, sz++ )
242 {
243 cass->image_read( &in, pos, 1 );
244 /* actually, we are bit laxist and treat as 0xff bytes with at least
245 5 bits out of 8 set to 1
246 */
247 for ( i = 0; in; in >>= 1 )
248 i += (in & 1);
249 if ( i < 5 )
250 break;
251 }
252
253 /* get block header */
254 if ( pos + 4 > size )
255 {
256 pos -= sz;
257 break;
258 }
259 cass->image_read( block, pos, 4 );
260 typ = block[2];
261 sz2 = block[3]+1;
262 if ( block[0] != 0x01 || block[1] != 0x3c || ( typ != 0x00 && typ != 0x01 && typ != 0xff ) )
263 {
264 pos -= sz;
265 break;
266 }
267 pos += 4;
268
269 /* get block */
270 cass->image_read( block+4, pos, sz2 );
271 pos += sz2;
272
273 /* 1-filler and 0xff-filler */
274 if ( typ == 0 || typ == 0xff )
275 K7_FILL_1( 1000 );
276 K7_FILL_ff( sz );
277
278 /* put block */
279 LOG (( "to7_k7_load: block off=$%x type=$%02X size=%i bitstart=%i\n", (int) pos-sz2-4, typ, sz2, to7_k7_bitsize ));
280 VLOG (( "to7_k7_load: data:" ));
281 for ( i = 0; i < sz2 + 4; i ++)
282 {
283 VLOG (( " $%02X", block[i] ));
284 K7_PUT_BYTE( block[i] );
285 }
286 VLOG (( "\n" ));
287
288 /* if it is a directory enty, says so to the user */
289 if ( typ == 0 )
290 {
291 char name[] = "01234567.ABC";
292 uint8_t t = block[15];
293 uint8_t u = block[16];
294 int p = (to7_k7_bitsize - sz2 - 4 - sz) * TO7_BIT_LENGTH;
295 memcpy( name, block+4, 8 );
296 memcpy( name+9, block+12, 3 );
297 for ( i = 0; name[i]; i++)
298 {
299 if ( name[i] < ' ' || name[i] >= 127 )
300 name[i] = '?';
301 }
302 PRINT (( "to7_k7_load: file \"%s\" type=%s,%s at %imn %is\n",
303 name,
304 (t==0) ? "bas" : (t==1) ? "dat" : (t==2) ? "bin" : "???",
305 (u == 0) ? "a" : (u == 0xff) ? "b" : "?",
306 p / 60, p % 60 ));
307 }
308
309 /* extra 1-fillers */
310 if ( typ == 0 || typ == 0xff )
311 K7_FILL_1( 1000 );
312 }
313
314 /* trailing data with invalid block structure
315 => dump it in a raw form, but stay alert for hidden block starts
316 */
317 if ( pos < size )
318 {
319 invalid++;
320 LOG (( "to7_k7_load: trailing raw bytes off=$%x bitstart=%i\n", (int) pos, to7_k7_bitsize ));
321
322 /* put block */
323 for (; pos < size; pos++ )
324 {
325 cass->image_read( &in, pos, 1 );
326 for ( sz = 0; pos < size && in == 0xff; sz++ )
327 {
328 pos++;
329 cass->image_read( &in, pos, 1 );
330 }
331 if ( invalid < 10 && sz > 4 && in == 0x01 && pos + 4 <= size )
332 {
333 uint8_t in1,in2;
334 cass->image_read( &in1, pos+1, 1 );
335 cass->image_read( &in2, pos+2, 1 );
336 if ( (in1 == 0x3c) && ((in2 == 0x00) || (in2 == 0x01) ) )
337 {
338 /* seems we have a regular block hidden in raw data => rebounce */
339 LOG (( "to7_k7_load: hidden regular block found\n" ));
340 pos -= sz;
341 goto rebounce;
342 }
343 if ( ( ( in1 == 0x3d ) && ( in2 == 0 ) ) || ( ( in1 == 0x57 ) && ( in2 == 0x49 ) ) )
344 {
345 /* special block (Infogrames) => just prepend filler */
346 K7_FILL_1 ( 500 );
347 LOG (( "to7_k7_load: special $%02X $%02X $%02X block found off=$%x bitstart=%i\n", in, in1, in2, (int) pos, to7_k7_bitsize ));
348 }
349 }
350 for ( i = 0; i < sz; i++ )
351 K7_PUT_BYTE( 0xff );
352 K7_PUT_BYTE( in );
353 }
354 }
355
356 if ( invalid )
357 PRINT (( "to7_k7_load: WARNING: the k7 has an unknown structure and may not work\n" ));
358
359 sz = to7_k7_bitsize * TO7_BIT_LENGTH;
360 PRINT (( "to7_k7_load: cassette length: %imn %is (%i samples)\n", sz / 60, sz % 60, to7_k7_bitsize ));
361
362 return cassette_image::error::SUCCESS;
363 }
364
365
366
367 static const cassette_image::Format to7_k7 =
368 { "k7", to7_k7_identify, to7_k7_load, nullptr /* no save */ };
369
370
371
372 /********************* TO7 WAV format ************************/
373
374
375
to7_wav_identify(cassette_image * cass,cassette_image::Options * opts)376 static cassette_image::error to7_wav_identify ( cassette_image *cass,
377 cassette_image::Options *opts )
378 {
379 cassette_image::error e = cassette_image::wavfile_format.identify( cass, opts );
380 return e;
381 }
382
383
384
to7_wav_load(cassette_image * cass)385 static cassette_image::error to7_wav_load ( cassette_image *cass )
386 {
387 cassette_image::error e = cassette_image::wavfile_format.load( cass );
388
389 if ( to7_k7_bits )
390 {
391 free( to7_k7_bits );
392 to7_k7_bits = nullptr;
393 }
394
395 if ( e != cassette_image::error::SUCCESS )
396 return e;
397
398 cassette_image::Info info = cass->get_info( );
399
400 double len = (double) info.sample_count / info.sample_frequency;
401
402 PRINT (( "to7_wav_load: loading cassette, length %imn %is, %i Hz, %i bps, %i bits\n",
403 (int) len / 60, (int) len % 60,
404 info.sample_frequency, info.bits_per_sample,
405 (int) (len / TO7_BIT_LENGTH) ));
406
407 return cassette_image::error::SUCCESS;
408 }
409
410
411
to7_wav_save(cassette_image * cass,const cassette_image::Info * info)412 static cassette_image::error to7_wav_save ( cassette_image *cass, const cassette_image::Info *info )
413 {
414 int len = info->sample_count / info->sample_frequency;
415 PRINT (( "to7_wav_save: saving cassette, length %imn %is, %i Hz, %i bps\n", len / 60, len % 60, info->sample_frequency, info->bits_per_sample ));
416 return cassette_image::wavfile_format.save( cass, info );
417 }
418
419
420
421 /* overloaded wav: dump info */
422 static const cassette_image::Format to7_wav =
423 { "wav", to7_wav_identify, to7_wav_load, to7_wav_save };
424
425
426
427 /************************* k5 format *************************/
428
429
430
mo5_k5_identify(cassette_image * cass,cassette_image::Options * opts)431 static cassette_image::error mo5_k5_identify ( cassette_image *cass,
432 cassette_image::Options *opts )
433 {
434 opts -> bits_per_sample = 8;
435 opts -> channels = 1;
436 opts -> sample_frequency = 22100;//11050;
437 return cassette_image::error::SUCCESS;
438 }
439
440
441
442 /* As the k7 format, the k5 format represents the stream of bytes returned by
443 the BIOS cassette routine and need to be hacked back into the original
444 cassette wave.
445
446 The format of blocks is a bit different from that of TO7 cassettes:
447 - 2-byte header is: 3C 5A
448 - 1-byte type
449 00 = start of file, the block contains the filename
450 01 = chunk of file data
451 FF = end of file
452 - 1-byte size
453 - size - 1 bytes of data (255 bytes of data if size = 0)
454 - one byte of CRC: sum from (and including) header up to (and including)
455 CRC byte should be 0 modulo 256
456
457 They are separated by 0x01 filler bytes (generally 10 of them),
458 for synchronisation. There are also extra 0 filler bits between certain
459 kinds of blocks, when the motor is supposed to go off and back on.
460 */
461
mo5_k5_load(cassette_image * cass)462 static cassette_image::error mo5_k5_load( cassette_image *cass )
463 {
464 size_t size = cass->image_size( ), pos = 0;
465 int i, sz, sz2, hbit = 0;
466 uint8_t in, in2, in3, typ, block[264], sum;
467 int invalid = 0, hbitsize = 0, dcmoto = 0;
468
469 LOG (( "mo5_k5_load: start conversion, size=%li\n", (long)size ));
470 PRINT (( "mo5_k5_load: open cassette, length: %li bytes\n", (long) size ));
471
472 /* store a half-bit */
473 #define K5_PUT_HBIT \
474 do \
475 { \
476 cass->put_sample ( 0, hbitsize * MO5_HBIT_LENGTH, MO5_HBIT_LENGTH, (hbit ? 1 : -1) << 30 ); \
477 hbitsize++; \
478 } while ( 0 )
479
480 /* store one bit */
481 #define K5_PUT_BIT( BIT ) \
482 do \
483 { \
484 if ( BIT ) \
485 { \
486 K5_PUT_HBIT; \
487 hbit = !hbit; \
488 K5_PUT_HBIT; \
489 } \
490 else \
491 { \
492 K5_PUT_HBIT; \
493 K5_PUT_HBIT; \
494 } \
495 hbit = !hbit; \
496 } while (0)
497
498 /* store one byte, no start / stop bit, converse order from TO7 */
499 #define K5_PUT_BYTE( BYTE ) \
500 do \
501 { \
502 uint8_t b = BYTE; \
503 int x; \
504 for ( x = 0; x < 8; x++ ) \
505 K5_PUT_BIT( (b >> (7 - x)) & 1 ); \
506 } while (0)
507
508 /* store filler */
509 #define K5_FILL_0( SIZE ) \
510 do \
511 { \
512 if ( (SIZE) > 0 ) \
513 { \
514 int j; \
515 LOG (( "mo5_k5_load: 0-filler size=%i hbitstart=%i\n", (SIZE), hbitsize )); \
516 for ( j = 0; j < (SIZE); j++ ) \
517 K5_PUT_BIT( 0 ); \
518 } \
519 } while (0)
520
521 #define K5_FILL_01( SIZE ) \
522 do \
523 { \
524 if ( (SIZE) > 0 ) \
525 { \
526 int j; \
527 LOG (( "mo5_k5_load: 0x01 filler size=%i bitstart=%i\n", (SIZE), hbitsize )); \
528 for ( j = 0; j < (SIZE); j++ ) \
529 K5_PUT_BYTE( 0x01 ); \
530 } \
531 } while (0)
532
533 /* check format */
534 cass->image_read( block, 0, 64 );
535 for ( i = 3; ; i++ )
536 {
537 if ( ( i >= size ) || ( i >= 64 ) )
538 {
539 /* ? */
540 PRINT (( "to5_k5_load: WARNING: this does not look like a MO or TO cassette.\n" ));
541 break;
542 }
543 else if ( ( block[i-3] == 0x01 ) && ( block[i-2] == 0x3c ) && ( block[i-1] == 0x5a ) && ! block[i] )
544 {
545 /* MO */
546 break;
547 }
548 else if ( ( block[i-3] == 0xff ) && ( block[i-2] == 0x01 ) && ( block[i-1] == 0x3c ) && ! block[i] )
549 {
550 /* TO */
551 PRINT (( "to5_k5_load: WARNING: this looks like a TO cassette, not a MO one.\n" ));
552 break;
553 }
554 }
555
556 cass->image_read( block, pos, 6 );
557 if ( ! memcmp( block, "DCMOTO", 6 ) || ! memcmp( block, "DCMO5", 5 ) || ! memcmp( block, "DCMO6", 5 ) )
558 dcmoto = 1;
559
560 /* loop over regular blocks */
561 while ( pos < size )
562 {
563 rebounce:
564 /* skip DCMOTO header*/
565 if ( dcmoto )
566 {
567 cass->image_read( block, pos, 6 );
568 if ( ! memcmp( block, "DCMOTO", 6 ) )
569 {
570 LOG (( "mo5_k5_load: DCMOTO signature found at off=$%x\n", (int)pos ));
571 pos += 6;
572 }
573 else if ( ! memcmp( block, "DCMO", 4 ) )
574 {
575 LOG (( "mo5_k5_load: DCMO* signature found at off=$%x\n", (int)pos ));
576 pos += 5;
577 }
578 }
579
580 /* skip 0x01 filler */
581 for ( sz = 0; pos < size; pos++, sz++ )
582 {
583 cass->image_read( &in, pos, 1 );
584 if ( in != 0x01 )
585 break;
586 }
587
588 /* get block header */
589 if ( pos + 4 > size )
590 {
591 pos -= sz;
592 break;
593 }
594 cass->image_read( block, pos, 4 );
595 typ = block[2];
596 sz2 = (uint8_t) (block[3]-1);
597 if ( block[0] != 0x3c || block[1] != 0x5a || ( typ != 0x00 && typ != 0x01 && typ != 0xff ) || pos+sz2 > size )
598 {
599 pos -= sz;
600 break;
601 }
602 pos += 4;
603
604 /* get block */
605 cass->image_read( block+4, pos, sz2 );
606 pos += sz2;
607
608 /* 0-fillers and 0x01-fillers */
609 if ( typ == 0 || typ == 0xff )
610 K5_FILL_0( 1200 );
611 else
612 K5_FILL_0( 300 ); /* for MO6 */
613 K5_FILL_01( sz < 10 ? 10 : sz );
614
615 /* put block */
616 LOG (( "mo5_k5_load: block off=$%x type=$%02X size=%i hbitstart=%i\n", (int) pos-sz2-4, typ, sz2, hbitsize ));
617 VLOG (( "mo5_k5_load: data:" ));
618 for ( i = 0; i < sz2 + 4; i ++)
619 {
620 VLOG (( " $%02X", block[i] ));
621 K5_PUT_BYTE( block[i] );
622 }
623 VLOG (( "\n" ));
624
625 /* checksum */
626 for ( i = 0, sum = 0; i < sz2; i++ )
627 sum += block[i+4];
628 if ( sum )
629 LOG(( "mo5_k5_load: invalid checksum $%02X (should be 0)\n", sum ));
630
631 /* if it is a directory enty, says so to the user */
632 if ( typ == 0 )
633 {
634 char name[] = "01234567.ABC";
635 uint8_t t = block[15];
636 uint8_t u = block[16];
637 int p = (hbitsize - sz2 - 4 - sz) * MO5_HBIT_LENGTH;
638 memcpy( name, block+4, 8 );
639 memcpy( name+9, block+12, 3 );
640 for ( i = 0; name[i]; i++)
641 {
642 if ( name[i] < ' ' || name[i] >= 127 )
643 name[i] = '?';
644 }
645 PRINT (( "mo5_k5_load: file \"%s\" type=%s,%s at %imn %is\n",
646 name,
647 (t==0) ? "bas" : (t==1) ? "dat" : (t==2) ? "bin" : "???",
648 (u == 0) ? "a" : (u == 0xff) ? "b" : "?",
649 p / 60, p % 60 ));
650 }
651
652 /* 0-fillers */
653 if ( typ == 0xff || typ == 0x00 )
654 K5_FILL_0( 1800 );
655 }
656
657 /* dump trailing bytes, but also look for beginnings of blocks */
658 if ( pos < size )
659 {
660 invalid++;
661 K5_FILL_0( 1200 );
662 LOG (( "mo5_k5_load: trailing trash off=$%x size=%i hbitstart=%i\n", (int) pos, (int) (size-pos), hbitsize ));
663 for ( ; pos < size; pos++ )
664 {
665 cass->image_read( &in, pos, 1 );
666 if ( dcmoto && in=='D' )
667 {
668 /* skip DCMOTO header*/
669 cass->image_read( block, pos, 6 );
670 if ( ! memcmp( block, "DCMOTO", 6 ) )
671 {
672 LOG (( "mo5_k5_load: DCMOTO signature found at off=$%x\n", (int)pos ));
673 pos += 6;
674 cass->image_read( &in, pos, 1 );
675 }
676 else if ( ! memcmp( block, "DCMO", 4 ) )
677 {
678 LOG (( "mo5_k5_load: DCMO* signature found at off=$%x\n", (int)pos ));
679 pos += 5;
680 cass->image_read( &in, pos, 1 );
681 }
682 }
683 for ( sz = 0; pos < size && in == 0x01; sz++ )
684 {
685 pos++;
686 cass->image_read( &in, pos, 1 );
687 }
688 if ( sz > 6 )
689 {
690 cass->image_read( &in2, pos+1, 1 );
691 cass->image_read( &in3, pos+2, 1 );
692 if ( invalid < 10 && in == 0x3c && in2 == 0x5a && (in3 == 0x00 || in3 == 0x01 || in3 == 0xff ) )
693 {
694 /* regular block found */
695 LOG (( "mo5_k5_load: hidden regular block found\n" ));
696 pos -= sz;
697 goto rebounce;
698 }
699 if ( invalid < 10 && sz > 6 && ( (in == 0x3c && in2 == 0x5a) || (in == 0xc3 && in2 == 0x5a) || (in == 0xc3 && in2 == 0x3c) || (in == 0x87 && in2 == 0x4a) ) )
700 {
701 /* special block found */
702 K5_FILL_0( 1200 );
703 LOG (( "mo5_k5_load: special block $%02X $%02X found off=$%x hbitstart=%i\n", in, in2, (int) pos-sz, hbitsize ));
704 }
705 }
706
707 VLOG (( "mo5_k5_load: special data:" ));
708 for ( i = 0; i < sz; i++ )
709 {
710 K5_PUT_BYTE( 0x01 );
711 VLOG (( " $01" ));
712 }
713 K5_PUT_BYTE( in );
714 VLOG (( " $%02X\n", in ));
715 }
716 }
717
718 if ( invalid )
719 PRINT (( "mo5_k5_load: WARNING: the k5 has an unknown structure and may not work\n" ));
720
721 sz = hbitsize * MO5_HBIT_LENGTH;
722 PRINT (( "mo5_k5_load: cassette length: %imn %is (%i half-bits)\n", sz / 60, sz % 60, hbitsize ));
723
724 return cassette_image::error::SUCCESS;
725 }
726
727
728
729 static const cassette_image::Format mo5_k5 =
730 { "k5,k7", mo5_k5_identify, mo5_k5_load, nullptr /* no save */ };
731
732
733 /********************* MO5 WAV format ************************/
734
735
736
mo5_wav_identify(cassette_image * cass,cassette_image::Options * opts)737 static cassette_image::error mo5_wav_identify ( cassette_image *cass,
738 cassette_image::Options *opts )
739 {
740 cassette_image::error e = cassette_image::wavfile_format.identify( cass, opts );
741 return e;
742 }
743
744
745
mo5_wav_load(cassette_image * cass)746 static cassette_image::error mo5_wav_load ( cassette_image *cass )
747 {
748 cassette_image::error e = cassette_image::wavfile_format.load( cass );
749 if ( e == cassette_image::error::SUCCESS )
750 {
751 cassette_image::Info info = cass->get_info( );
752 int len = info.sample_count / info.sample_frequency;
753 PRINT (( "mo5_wav_load: loading cassette, length %imn %is, %i Hz, %i bps\n", len / 60, len % 60, info.sample_frequency, info.bits_per_sample ));
754 }
755 return e;
756 }
757
758
759
mo5_wav_save(cassette_image * cass,const cassette_image::Info * info)760 static cassette_image::error mo5_wav_save ( cassette_image *cass, const cassette_image::Info *info )
761 {
762 int len = info->sample_count / info->sample_frequency;
763 PRINT (( "mo5_wav_save: saving cassette, length %imn %is, %i Hz, %i bps\n", len / 60, len % 60, info->sample_frequency, info->bits_per_sample ));
764 return cassette_image::wavfile_format.save( cass, info );
765 }
766
767
768
769 /* overloaded wav: dump info */
770 static const cassette_image::Format mo5_wav =
771 { "wav", mo5_wav_identify, mo5_wav_load, mo5_wav_save };
772
773
774
775 /********************* formats ************************/
776
777
778 const cassette_image::Format *const to7_cassette_formats[] =
779 { &to7_wav, &to7_k7, nullptr };
780
781
782 const cassette_image::Format *const mo5_cassette_formats[] =
783 { &mo5_wav, &mo5_k5, nullptr };
784