summaryrefslogtreecommitdiffstatshomepage
path: root/src/lib/formats/coco_cas.cpp
blob: 6579f47a60fc20d33896131322f002d37cb4d09a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
// license:BSD-3-Clause
// copyright-holders:Nathan Woods
/**************************************************************************

    coco_cas.c

    Format code for CoCo CAS (*.cas) files

    This is the actual code that processes the CAS data.  CAS files are a
    problem because they are a legacy of previous CoCo emulators, and most
    of these emulators patch the system as a shortcut.  Thus, the ones and
    zeroes in the file do not truly represent modulated pulses in the
    waveform, but what the BIOS reads and writes.

    In doing so, they make the CoCo more tolerant of short headers and lack
    of delays between blocks.  This legacy is reflected in most CAS files in
    use, and thus presents a problem for a pure hardware emulation like MESS.

    One alternative is to preprocess the data on the CAS, file by file, but
    this proves to be problematic because in the process, legitimate data
    that is unrecognized by the preprocessor may get dropped.

    The approach taken here is a hybrid approach - it retrieves the data
    block by block until an error occurs; be it the end of the CAS or a
    corrupt (?!) block.  When "legitimate" blocks are done processing, the
    remainder of the data is added to the waveform in a traditional manner.
    The result has proven to work quite well.

    2005-May-04, P.Harvey-Smith, improved handling of some "copy protected"
    Dragon games that have odd blocks in odd places, this has slightly
    increased loading time but does mean games like "Rommel's Revenge" will
    now load.

**************************************************************************/

#include "emu.h" // PAIR
#include "coco_cas.h"

#define COCO_WAVESAMPLES_HEADER     (1.0)
#define COCO_WAVESAMPLES_TRAILER    (1.0)
#define COCO_LONGSILENCE            (5.0)

//some games load with only 5s, but most games need 15s
#define ALICE32_WAVESAMPLES_HEADER  (15.0)

static int synccount;

const struct CassetteModulation coco_cas_modulation =
{
	CASSETTE_MODULATION_SINEWAVE,
	600.0,  1200.0, 1500.0,
	1500.0, 2400.0, 3000.0
};



static cassette_image::error coco_cas_identify(cassette_image *cassette, struct CassetteOptions *opts)
{
	return cassette_modulation_identify(cassette, &coco_cas_modulation, opts);
}



static bool get_cas_block(cassette_image *cassette, uint64_t *offset, uint8_t *block, int *block_len)
{
	uint8_t block_length = 0;
	uint8_t block_checksum = 0;
	uint64_t current_offset;
	uint64_t image_size;
	PAIR p;
	int i;
	int state = 0;
	int phase = 0;

	synccount = 0;
	p.w.l = 0;
	image_size = cassette_image_size(cassette);
	current_offset = *offset;

	while(current_offset < image_size)
	{
		cassette_image_read(cassette, &p.b.h, current_offset, 1);
		current_offset++;

		for (i = 0; i < 8; i++)
		{
			p.w.l >>= 1;

			if (state == 0)
			{
				/* searching for a block */
				if (p.b.l == 0x3C)
				{
					/* found one! */
					phase = i;
					state++;
				}
				else if (p.b.l == 0x55)
				{
					synccount++;
				}
			}
			else if (i == phase)
			{
				*(block++) = p.b.l;
				switch(state) {
				case 1:
					/* found file type */
					block_checksum = p.b.l;
					state++;
					break;
				case 2:
					/* found file size */
					block_length = p.b.l;
					*block_len = ((int) block_length) + 3;
					block_checksum += p.b.l;
					state++;
					break;

				case 3:
					/* data byte */
					if (block_length)
					{
						block_length--;
						block_checksum += p.b.l;
					}
					else
					{
						/* end of block */
						if (p.b.l != block_checksum)
						{
							/* checksum failure */
							return false;
						}
						else
						{
							/* checksum success */
							*offset = current_offset;
							return true;
						}
					}
				}
			}
		}
	}

	/* no more blocks */
	return false;
}



static cassette_image::error cas_load(cassette_image *cassette, uint8_t silence)
{
	cassette_image::error err;
	uint64_t offset;
	uint64_t image_size;
	uint8_t block[258];   /* 255 bytes per block + 3 (type, length, checksum) */
	int block_length = 0;
	uint8_t last_blocktype;
	double time_index = 0.0;
	double time_displacement;
	static const uint8_t magic_bytes[2] = { 0x55, 0x3C };

#if 0
	{
		static const uint8_t dummy_bytes[] =
		{
			0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
			0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
			0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
			0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
			0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
			0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
			0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
			0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
			0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
			0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
			0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
			0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
			0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
			0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
			0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
			0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
			0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
			0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
			0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
			0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
			0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
			0x3C, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A
		};
		time_index = 10.0;
		return cassette_put_modulated_data(cassette, 0, time_index, dummy_bytes, sizeof(dummy_bytes), &coco_cas_modulation, &time_displacement);
	}
#endif

	err = cassette_put_sample(cassette, 0, time_index, COCO_WAVESAMPLES_HEADER, 0);
	if (err != cassette_image::error::SUCCESS)
		return err;
	time_index += COCO_WAVESAMPLES_HEADER;

	offset = 0;
	last_blocktype = 0;
	image_size = cassette_image_size(cassette);

	/* try to find a block that we can untangle */
	while(get_cas_block(cassette, &offset, block, &block_length))
	{
		/* Forcing a silence before a filename block, improves the ability to load some */
		/* copy protected Dragon games, e.g. Rommel's Revenge */
		/* was the last block a filename block? */
		if ((last_blocktype == 0) || (last_blocktype == 0xFF) || (block[0] == 0))
		{
			/* silence */
			err = cassette_put_sample(cassette, 0, time_index, silence, 0);
			if (err != cassette_image::error::SUCCESS)
				return err;
			time_index += silence;

			/* sync data */
			err = cassette_put_modulated_filler(cassette, 0, time_index, 0x55, 128, &coco_cas_modulation, &time_displacement);
			if (err != cassette_image::error::SUCCESS)
				return err;
			time_index += time_displacement;
		}
		else if (synccount != 0)        /* If we have multiple sync bytes in cas file, make sure they */
		{               /* are passed through */
			/* sync data */
			err = cassette_put_modulated_filler(cassette, 0, time_index, 0x55, synccount, &coco_cas_modulation, &time_displacement);
			if (err != cassette_image::error::SUCCESS)
				return err;
			time_index += time_displacement;
		}

		/* now fill in the magic bytes */
		err = cassette_put_modulated_data(cassette, 0, time_index, magic_bytes, sizeof(magic_bytes), &coco_cas_modulation, &time_displacement);
		if (err != cassette_image::error::SUCCESS)
			return err;
		time_index += time_displacement;

		/* now fill in the block */
		err = cassette_put_modulated_data(cassette, 0, time_index, block, block_length, &coco_cas_modulation, &time_displacement);
		if (err != cassette_image::error::SUCCESS)
			return err;
		time_index += time_displacement;

		/* and the last magic byte */
		err = cassette_put_modulated_filler(cassette, 0, time_index, 0x55, 1, &coco_cas_modulation, &time_displacement);
		if (err != cassette_image::error::SUCCESS)
			return err;
		time_index += time_displacement;

		last_blocktype = block[0];
	}

	/* all futher data is undecipherable, so output it verbatim */
	err = cassette_read_modulated_data(cassette, 0, time_index, offset, image_size - offset, &coco_cas_modulation, &time_displacement);
	if (err != cassette_image::error::SUCCESS)
		return err;
	time_index += time_displacement;

	return cassette_image::error::SUCCESS;
}

static cassette_image::error coco_cas_load(cassette_image *cassette)
{
	return cas_load(cassette, COCO_WAVESAMPLES_HEADER);
}

static cassette_image::error alice32_cas_load(cassette_image *cassette)
{
	return cas_load(cassette, ALICE32_WAVESAMPLES_HEADER);
}

const struct CassetteFormat coco_cas_format =
{
	"cas",
	coco_cas_identify,
	coco_cas_load,
	nullptr
};

const struct CassetteFormat alice32_cas_format =
{
	"cas,c10,k7",
	coco_cas_identify,
	alice32_cas_load,
	nullptr
};


CASSETTE_FORMATLIST_START(coco_cassette_formats)
	CASSETTE_FORMAT(coco_cas_format)
CASSETTE_FORMATLIST_END

CASSETTE_FORMATLIST_START(alice32_cassette_formats)
	CASSETTE_FORMAT(alice32_cas_format)
CASSETTE_FORMATLIST_END