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