mirror of https://github.com/pevans/erc-c.git
311 lines
11 KiB
C
311 lines
11 KiB
C
/*
|
|
* apple2.enc.c
|
|
*
|
|
* Encode disk image data with 6-and-2 encoding, which in effect
|
|
* "nibbilizes" them (in exactly the way a .NIB file is saved). This
|
|
* process is somewhat complicated, and I don't blame you if you gently
|
|
* bop your head on your desk and ask woz, pleadingly, why, why, why.
|
|
*
|
|
* The reason for "why" is that early disk drives were pretty limited,
|
|
* and specifically could not have zero bits next to each other. Which
|
|
* is _crazy_. Hence the crazy code below.
|
|
*/
|
|
|
|
#include "apple2/enc.h"
|
|
#include "apple2/dd.h"
|
|
#include "vm_segment.h"
|
|
|
|
/*
|
|
* This is the table that holds the bytes that represent 6-and-2 encoded
|
|
* data. Note the table goes from $00..$3F; that is the amount of values
|
|
* that six bits can hold. Each of those six-bit combinations maps to a
|
|
* different byte value that would be literally written to and read from
|
|
* the disk media. Apple II's RWTS subroutine would then translate them
|
|
* back into data that is useful to the software being run.
|
|
*
|
|
* Also, since I forget: gcr is short for "group coded recording"
|
|
*/
|
|
static vm_8bit gcr62[] = {
|
|
// 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
|
|
0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6, 0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3, // 00
|
|
0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3, // 10
|
|
0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec, // 20
|
|
0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, // 30
|
|
};
|
|
|
|
/*
|
|
* Define the physical sector order in which we write encoded data
|
|
*/
|
|
static int physical_order[] = {
|
|
0x0, 0xD, 0xB, 0x9, 0x7, 0x5, 0x3, 0x1,
|
|
0xE, 0xC, 0xA, 0x8, 0x6, 0x4, 0x2, 0xF,
|
|
};
|
|
|
|
/*
|
|
* Encode the given DOS-formatted segment with 6-and-2 encoding. This
|
|
* can work for both DOS 3.3 and ProDOS images, but would fail with DOS
|
|
* 3.2 and 3.1 (which use 5-and-3 encoding).
|
|
*/
|
|
vm_segment *
|
|
apple2_enc_dos(int type, vm_segment *src)
|
|
{
|
|
vm_segment *dest;
|
|
int i, doff = 0;
|
|
|
|
if (src == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
// Use the nibbilized size for a 140k image file
|
|
dest = vm_segment_create(_140K_NIB_);
|
|
|
|
// Each of DOS 3.3 and ProDOS have the same sizes, but they use
|
|
// different terminology; for example, ProDOS has a number of
|
|
// 512-byte blocks, and DOS 3.3 has a number of sectors and tracks.
|
|
// But they all add up to the same number of bytes, and we can get
|
|
// away with just using tracks-and-sectors. In particular, DOS 3.3
|
|
// has 35 tracks of 4096 bytes each.
|
|
for (i = 0; i < 35; i++) {
|
|
doff += apple2_enc_track(type, dest, src, doff, i);
|
|
}
|
|
|
|
return dest;
|
|
}
|
|
|
|
/*
|
|
* Return a segment that is properly encoded given a segment containin g
|
|
* the data from a NIB file.
|
|
*
|
|
* This function is almost not really necessary, but it exists to
|
|
* present a wrapper in case we want to do something else with NIB
|
|
* files. But, to the point--NIB files are essentially DOS files that
|
|
* have _already_ been 6-and-2 encoded. This is generally done because
|
|
* whatever software on the disk image has copy protection that requires
|
|
* certain "magic bytes" exist in the sector gaps that have self-sync
|
|
* bytes, that older Apple II copy programs would skip.
|
|
*
|
|
* Here we return a deep copy of the src segment; it'll be a different
|
|
* pointer to a different segment in memory. This is the same
|
|
* (necessary) behavior in the enc_dos function, even if it may not seem
|
|
* necessary on the surface for this function. You must, therefore, be
|
|
* sure you free the segment returned from this function when you are
|
|
* finished with it.
|
|
*/
|
|
vm_segment *
|
|
apple2_enc_nib(vm_segment *src)
|
|
{
|
|
vm_segment *dest;
|
|
|
|
// No src segment, no return data.
|
|
if (src == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
dest = vm_segment_create(src->size);
|
|
vm_segment_copy(dest, src, 0, 0, src->size);
|
|
|
|
return dest;
|
|
}
|
|
|
|
/*
|
|
* Encode one specific track from the src segment with 6-and-2 encoding
|
|
* into the dest segment, and return the number of bytes that was
|
|
* written into dest.
|
|
*/
|
|
int
|
|
apple2_enc_track(int sectype, vm_segment *dest, vm_segment *src,
|
|
int doff, int track)
|
|
{
|
|
int toff = track * ENC_DTRACK;
|
|
int orig = doff;
|
|
int sect, i, soff;
|
|
|
|
// We'll start off with some self-sync bytes to separate this track
|
|
// from any other
|
|
for (i = 0; i < 48; i++) {
|
|
vm_segment_set(dest, doff++, 0xff);
|
|
}
|
|
|
|
for (sect = 0; sect < 16; sect++) {
|
|
int logsec = apple2_dd_sector_num(sectype, sect);
|
|
int physec = physical_order[sect];
|
|
|
|
soff = toff + (ENC_DSECTOR * logsec);
|
|
doff = orig + (ENC_ESECTOR * physec) + 48;
|
|
|
|
// Each sector has a header with some metadata, plus some
|
|
// markers and padding.
|
|
doff += apple2_enc_sector_header(dest, doff, track, sect);
|
|
doff += apple2_enc_sector(dest, src, doff, soff);
|
|
}
|
|
|
|
return ENC_ETRACK;
|
|
}
|
|
|
|
/*
|
|
* Encode the src segment of image data (e.g. from a disk) with 6-and-2
|
|
* encoding; this will copy one 256 byte block from src into a 343-byte
|
|
* block into dest. We work the given destination offset and source
|
|
* offset, but care must be taken to ensure that dest contains enough
|
|
* room to hold the values from src.
|
|
*/
|
|
int
|
|
apple2_enc_sector(vm_segment *dest, vm_segment *src,
|
|
int doff, int soff)
|
|
{
|
|
int i, di, orig;
|
|
vm_8bit lastval, curval;
|
|
|
|
orig = doff;
|
|
|
|
// The init array contains the src segment's 256 bytes converted
|
|
// into 342 bytes, but more works needs to be done to get it into
|
|
// proper 6-and-2 encoding. The xor array will contain the XOR'd
|
|
// version of init, but with an extra value tagged in as a checksum.
|
|
vm_8bit init[0x156], xor[0x157];
|
|
|
|
// This loop is really complicated; I'll annotate it as best I can.
|
|
// To begin with, we mean to write the first 86 bytes for the
|
|
// initial array.
|
|
for (i = 0; i < 0x56; i++) {
|
|
vm_8bit v = 0, vac, v56, v00;
|
|
vm_8bit offac, off56;
|
|
|
|
// We compute the offsets for the values beginning at ac and 56
|
|
// here, specifically in 8bit variables, because we _want_ any
|
|
// overflow to be handled within the offset rather than spilling
|
|
// beyond the border of the segment.
|
|
offac = i + 0xAC;
|
|
off56 = i + 0x56;
|
|
|
|
// We do the write by working with the src segment in rough
|
|
// thirds. vac is the value offset by 0xAC, which is 0x56 * 2.
|
|
// v56 is offset by 0x56, and v00 has no offset. In decimal
|
|
// terms, vac is 172 bytes offset from 0, and v56 is 86 bytes
|
|
// offset from 0.
|
|
vac = vm_segment_get(src, soff + offac);
|
|
v56 = vm_segment_get(src, soff + off56);
|
|
v00 = vm_segment_get(src, soff + i);
|
|
|
|
// The value we ultimately want to write into the dest segment
|
|
// is then mangled a bit. v begins life as zero, of course; it's
|
|
// then OR'd with vac's first and second bits, but in reverse
|
|
// order as it were; that is, bit 0 is promoted to bit 1 in v,
|
|
// and bit 1 in vac is demoted to bit 0 in v. This is repeated
|
|
// twice more, with v56 and v00. We now have filled in six bits.
|
|
v = (v << 2) | ((vac & 0x1) << 1) | ((vac & 0x2) >> 1);
|
|
v = (v << 2) | ((v56 & 0x1) << 1) | ((v56 & 0x2) >> 1);
|
|
v = (v << 2) | ((v00 & 0x1) << 1) | ((v00 & 0x2) >> 1);
|
|
|
|
// We then write this into the dest segment, shifted twice more,
|
|
// so that all the bits that may be high will begin at the
|
|
// "left" side, from bit 7 - bit 2, leaving bit 1 and 0 at zero.
|
|
init[i] = v << 2;
|
|
}
|
|
|
|
// The last two bytes written must be AND'd so that only the first
|
|
// six bits can be high. But because the bit 1 and 0 are already
|
|
// low, this has the effect of limiting the high bits to bits 5-2.
|
|
init[i-2] &= 0x3F;
|
|
init[i-1] &= 0x3F;
|
|
|
|
// The rest of the bytes may be copied from the src buffer into dest
|
|
// without modification. (Phew!)
|
|
for (i = 0x00, di = 0x56; i < 0x100; i++, di++) {
|
|
init[di] = vm_segment_get(src, soff+i);
|
|
}
|
|
|
|
// Here we will XOR each byte with each successive byte, and store
|
|
// that into the xor array.
|
|
for (i = 0, lastval = 0; i < 0x156; i++) {
|
|
curval = init[i];
|
|
xor[i] = curval ^ lastval;
|
|
lastval = curval;
|
|
}
|
|
|
|
// But we need one more byte in the xor array; this is just the last
|
|
// value from init.
|
|
xor[i] = lastval;
|
|
|
|
// This is the marker of the beginning of sector data
|
|
vm_segment_set(dest, doff++, 0xd5);
|
|
vm_segment_set(dest, doff++, 0xaa);
|
|
vm_segment_set(dest, doff++, 0xad);
|
|
|
|
// Now we use the gcr table for 6-and-2 encoding to take the XOR'd
|
|
// values and represent them as they should be in the destination
|
|
// segment. This constitutes the data field of the sector.
|
|
for (i = 0; i < 0x157; i++) {
|
|
vm_segment_set(dest, doff++, gcr62[xor[i] >> 2]);
|
|
}
|
|
|
|
// These three bytes mark the end of the data field
|
|
vm_segment_set(dest, doff++, 0xde);
|
|
vm_segment_set(dest, doff++, 0xaa);
|
|
vm_segment_set(dest, doff++, 0xeb);
|
|
|
|
// At the conclusion of a sector, we write 48 self-sync bytes.
|
|
for (i = 0; i < 48; i++) {
|
|
vm_segment_set(dest, doff++, 0xff);
|
|
}
|
|
|
|
return doff - orig;
|
|
}
|
|
|
|
/*
|
|
* Encode one byte with 4-and-4 encoding into the given segment and
|
|
* offset. The metadata for a track or sector, when they are encoded at
|
|
* all, are encoded with 4-and-4, which is much (!) simpler than 6-and-2
|
|
* but less space-efficient by quite a bit. The number of bytes consumed
|
|
* is returned, but it is always 2 bytes for every one byte given.
|
|
*/
|
|
int
|
|
apple2_enc_4n4(vm_segment *seg, int off, vm_8bit val)
|
|
{
|
|
vm_segment_set(seg, off, ((val >> 1) & 0x55) | 0xaa);
|
|
vm_segment_set(seg, off+1, (val & 0x55) | 0xaa);
|
|
|
|
// 4n4 encoding always consumes two bytes
|
|
return 2;
|
|
}
|
|
|
|
/*
|
|
* Encode the header for a track sector. This has two purposes; one, it
|
|
* demarcates one sector from another; two, it includes metadata about
|
|
* the disk volume, track number, and sector number that can be checked
|
|
* against what the computer thinks those should be and thus help it
|
|
* ensure a proper reality.
|
|
*/
|
|
int
|
|
apple2_enc_sector_header(vm_segment *seg, int off,
|
|
int track, int sect)
|
|
{
|
|
int orig = off;
|
|
|
|
// This is the "prologue" for the sector header, as WinApple calls
|
|
// it. This is always the same hardcoded set of bytes.
|
|
vm_segment_set(seg, off++, 0xd5);
|
|
vm_segment_set(seg, off++, 0xaa);
|
|
vm_segment_set(seg, off++, 0x96);
|
|
|
|
// Our metadata, all encoded in 4-and-4.
|
|
off += apple2_enc_4n4(seg, off, 0xfe);
|
|
off += apple2_enc_4n4(seg, off, track);
|
|
off += apple2_enc_4n4(seg, off, sect);
|
|
off += apple2_enc_4n4(seg, off, 0xfe ^ track ^ sect);
|
|
|
|
// Finish off with an "epilogue". Like the prologue, this is a
|
|
// hardcoded set of bytes.
|
|
vm_segment_set(seg, off++, 0xde);
|
|
vm_segment_set(seg, off++, 0xaa);
|
|
vm_segment_set(seg, off++, 0xeb);
|
|
|
|
// Write six (exactly six!) self-sync bytes following the epilogue,
|
|
// because the Disk II controller/RWTS method expect to find it.
|
|
for (int i = 0; i < 5; i++) {
|
|
vm_segment_set(seg, off++, 0xff);
|
|
}
|
|
|
|
return off - orig;
|
|
}
|