diff options
Diffstat (limited to 'src/lib/formats/sap_dsk.cpp')
-rw-r--r-- | src/lib/formats/sap_dsk.cpp | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/src/lib/formats/sap_dsk.cpp b/src/lib/formats/sap_dsk.cpp new file mode 100644 index 00000000000..6809b8bab29 --- /dev/null +++ b/src/lib/formats/sap_dsk.cpp @@ -0,0 +1,168 @@ +// license:BSD-3-Clause +// copyright-holders:AJR + +#include "sap_dsk.h" + +#include "ioprocs.h" +#include "multibyte.h" + +static constexpr unsigned HEADER_LENGTH = 66; +static constexpr uint8_t MAGIC_XOR = 0xb3; + +const sap_dsk_format FLOPPY_SAP_FORMAT; + +inline uint16_t sap_dsk_format::accumulate_crc(uint16_t crc, uint8_t data) noexcept +{ + // Almost CRC16-CCITT, but not quite... + for (int shift : {0, 4}) + crc = ((crc >> 4) & 0xfff) ^ 0x1081 * ((crc ^ (data >> shift)) & 0xf); + return crc; +} + +const char *sap_dsk_format::name() const noexcept +{ + return "sap_dsk"; +} + +const char *sap_dsk_format::description() const noexcept +{ + return "Thomson SAP disk image (Systeme d'Archivage Pukall)"; +} + +const char *sap_dsk_format::extensions() const noexcept +{ + return "sap"; +} + +bool sap_dsk_format::supports_save() const noexcept +{ + return false; +} + +int sap_dsk_format::identify(util::random_read &io, uint32_t form_factor, const std::vector<uint32_t> &variants) const +{ + uint8_t buffer[HEADER_LENGTH]; + auto [err, actual] = read_at(io, 0, buffer, HEADER_LENGTH); + if (err || actual != HEADER_LENGTH) + return 0; + + // Check ID string + if (memcmp(&buffer[1], "SYSTEME D'ARCHIVAGE PUKALL S.A.P. (c) Alexandre PUKALL Avril 1998", HEADER_LENGTH - 1) != 0) + return 0; + + // Check disk format + if (form_factor == floppy_image::FF_35) + { + // 3.5" disks must have 80 double-density tracks + if ((buffer[0] & 0x7f) != 0x01) + return 0; + } + else if (form_factor == floppy_image::FF_525) + { + // 5.25" disks must have 40 tracks + if ((buffer[0] & 0x7e) != 0x02) + return 0; + } + else if (form_factor != floppy_image::FF_UNKNOWN) + return 0; + + uint32_t expected_size = HEADER_LENGTH + uint32_t((buffer[0] & 0x01) ? 256 + 6 : 128 + 6) * ((buffer[0] & 0x02) ? 40 : 80) * 16 * ((buffer[0] & 0x80) ? 2 : 1); + uint64_t actual_size; + if (!io.length(actual_size) && expected_size == actual_size) + return FIFID_SIGN | FIFID_SIZE; + else + return FIFID_SIGN; +} + +bool sap_dsk_format::load(util::random_read &io, uint32_t form_factor, const std::vector<uint32_t> &variants, floppy_image &image) const +{ + uint8_t disk_type; + if (auto [err, actual] = read_at(io, 0, &disk_type, 1); err || actual != 1) + return false; + + int track_count = (disk_type & 0x02) ? 40 : 80; + int head_count = (disk_type & 0x80) ? 2 : 1; + int max_tracks, max_heads; + image.get_maximal_geometry(max_tracks, max_heads); + if (max_heads < head_count || max_tracks < track_count) + return false; + + switch (disk_type & 0x03) + { + case 0x01: + image.set_form_variant(floppy_image::FF_35, (disk_type & 0x80) ? floppy_image::DSDD : floppy_image::SSDD); + break; + + case 0x02: + image.set_form_variant(floppy_image::FF_525, (disk_type & 0x80) ? floppy_image::DSSD : floppy_image::SSSD); + break; + + case 0x03: + image.set_form_variant(floppy_image::FF_525, (disk_type & 0x80) ? floppy_image::DSDD : floppy_image::SSDD); + break; + + default: + return false; + } + + uint8_t sector_buf[16 * 1024]; + uint8_t sector_header[4]; + uint8_t sector_crc[2]; + desc_pc_sector sectors[16]; + + size_t read_offset = HEADER_LENGTH; + for (int head = 0; head < head_count; head++) + { + for (int track = 0; track < track_count; track++) + { + uint8_t *bufptr = §or_buf[0]; + uint8_t track_size = 16; + int sector_count = 0; + bool is_mfm = disk_type & 0x01; + while (track_size != 0) + { + if (auto [err, actual] = read_at(io, read_offset, §or_header[0], 4); err || actual != 4) + return false; + if (sector_count == 0 && sector_header[0] == 1) + is_mfm = false; + uint8_t sector_size = (sector_header[0] & 2) ? 4 >> (sector_header[0] & 1) : 1; + if (track_size < sector_size) + return false; + uint16_t sector_octets = sector_size * (is_mfm ? 256 : 128); + if (auto [err, actual] = read_at(io, read_offset + 4, bufptr, sector_octets); err || actual != sector_octets) + return false; + if (auto [err, actual] = read_at(io, read_offset + 4 + sector_octets, §or_crc[0], 2); err || actual != 2) + return false; + + uint16_t crc = 0xffff; + for (uint8_t data : sector_header) + crc = accumulate_crc(crc, data); + for (uint16_t i = 0; i < sector_octets; i++) + { + bufptr[i] ^= MAGIC_XOR; + crc = accumulate_crc(crc, bufptr[i]); + } + + sectors[sector_count].track = sector_header[2]; + sectors[sector_count].head = head; + sectors[sector_count].sector = sector_header[3]; + sectors[sector_count].size = sector_size; + sectors[sector_count].actual_size = sector_octets; + sectors[sector_count].data = bufptr; + sectors[sector_count].deleted = false; + sectors[sector_count].bad_crc = crc != get_u16be(sector_crc); + + read_offset += sector_octets + 6; + bufptr += sector_octets; + track_size -= sector_size; + sector_count++; + } + if (is_mfm) + build_pc_track_mfm(track, head, image, 100000, sector_count, sectors, calc_default_pc_gap3_size(form_factor, sectors[0].actual_size)); + else + build_pc_track_fm(track, head, image, 100000 / 2, sector_count, sectors, calc_default_pc_gap3_size(form_factor, sectors[0].actual_size)); + } + } + + return true; +} |