/* * 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; }