summaryrefslogtreecommitdiffstatshomepage
path: root/trunk/src/lib/formats/coco_cas.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/src/lib/formats/coco_cas.c')
-rw-r--r--trunk/src/lib/formats/coco_cas.c313
1 files changed, 313 insertions, 0 deletions
diff --git a/trunk/src/lib/formats/coco_cas.c b/trunk/src/lib/formats/coco_cas.c
new file mode 100644
index 00000000000..441c8620ae1
--- /dev/null
+++ b/trunk/src/lib/formats/coco_cas.c
@@ -0,0 +1,313 @@
+/**************************************************************************
+
+ 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 "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)
+
+// PAIR is an endian-safe union useful for representing 32-bit CPU registers
+union PAIR
+{
+#ifdef LSB_FIRST
+ struct { UINT8 l,h,h2,h3; } b;
+ struct { UINT16 l,h; } w;
+ struct { INT8 l,h,h2,h3; } sb;
+ struct { INT16 l,h; } sw;
+#else
+ struct { UINT8 h3,h2,h,l; } b;
+ struct { INT8 h3,h2,h,l; } sb;
+ struct { UINT16 h,l; } w;
+ struct { INT16 h,l; } sw;
+#endif
+ UINT32 d;
+ INT32 sd;
+};
+
+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 casserr_t coco_cas_identify(cassette_image *cassette, struct CassetteOptions *opts)
+{
+ return cassette_modulation_identify(cassette, &coco_cas_modulation, opts);
+}
+
+
+
+static int get_cas_block(cassette_image *cassette, UINT64 *offset, UINT8 *block, int *block_len)
+{
+ UINT8 block_length = 0;
+ UINT8 block_checksum = 0;
+ UINT64 current_offset;
+ UINT64 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 casserr_t cas_load(cassette_image *cassette, UINT8 silence)
+{
+ casserr_t err;
+ UINT64 offset;
+ UINT64 image_size;
+ UINT8 block[258]; /* 255 bytes per block + 3 (type, length, checksum) */
+ int block_length = 0;
+ UINT8 last_blocktype;
+ double time_index = 0.0;
+ double time_displacement;
+ static const UINT8 magic_bytes[2] = { 0x55, 0x3C };
+
+#if 0
+ {
+ static const UINT8 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)
+ 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)
+ 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)
+ 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)
+ 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)
+ 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)
+ 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)
+ 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)
+ return err;
+ time_index += time_displacement;
+
+ return CASSETTE_ERROR_SUCCESS;
+}
+
+static casserr_t coco_cas_load(cassette_image *cassette)
+{
+ return cas_load(cassette, COCO_WAVESAMPLES_HEADER);
+}
+
+static casserr_t 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,
+ NULL
+};
+
+const struct CassetteFormat alice32_cas_format =
+{
+ "cas,c10,k7",
+ coco_cas_identify,
+ alice32_cas_load,
+ NULL
+};
+
+
+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