mirror of
https://github.com/holub/mame
synced 2025-05-22 21:58:57 +03:00
314 lines
9.0 KiB
C
314 lines
9.0 KiB
C
/**************************************************************************
|
|
|
|
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
|