// // Media.m // Ample // // Created by Kelvin Sherlock on 3/7/2021. // Copyright © 2021 Kelvin Sherlock. All rights reserved. // #import #import "Media.h" const Media EmptyMedia = { 0 }; struct Media MediaFromDictionary(NSDictionary *dict) { Media m = { 0 }; #define _(name) m.name = [[dict objectForKey: @ # name] unsignedIntValue] _(cass); _(cdrom); _(hard); _(floppy_3_5); _(floppy_5_25); _(floppy_8); _(pseudo_disk); _(bitbanger); _(midiin); _(midiout); _(picture); _(rom); return m; #undef _ } void MediaAdd(Media *dest, const Media *src) { if (!src || !dest) return; // could merge from src media but not currently set there. //unsigned count; unsigned dest_flops = dest->floppy_8 + dest->floppy_5_25 + dest->floppy_3_5; unsigned src_flops = src->floppy_8 || src->floppy_5_25 || src->floppy_3_5; #define _(name) dest->name += src->name; _(cass); _(cdrom); _(hard); _(floppy_3_5); _(floppy_5_25); _(floppy_8); _(pseudo_disk); _(bitbanger); _(midiin); _(midiout); _(picture); _(rom); #undef _ // this assumes any device that supports more than 1 floppy size // the order is 8" < 5.25" < 3.5" if (src_flops) { uint64_t bits = 1 << dest_flops; for (unsigned i = 0; i < src->floppy_8; ++i) { dest->floppy_mask_8 |= bits; bits <<= 1; } for (unsigned i = 0; i < src->floppy_5_25; ++i) { dest->floppy_mask_5_25 |= bits; bits <<= 1; } for (unsigned i = 0; i < src->floppy_3_5; ++i) { dest->floppy_mask_3_5 |= bits; bits <<= 1; } } } BOOL MediaEqual(const Media *lhs, const Media *rhs) { if (lhs == rhs) return YES; if (!lhs || !rhs) return NO; #define _(name) if (lhs->name != rhs->name) return NO; _(cass); _(cdrom); _(hard); _(floppy_3_5); _(floppy_5_25); _(floppy_8); _(pseudo_disk); _(bitbanger); _(midiin); _(midiout); _(picture); _(rom); _(floppy_mask_8); _(floppy_mask_5_25); _(floppy_mask_3_5); return YES; #undef _ } #include #include #define _x2(a,b) (a | (b << 8)) #define _x3(a,b,c) (a | (b << 8) | (c << 16)) #define _x4(a,b,c, d) (a | (b << 8) | (c << 16) | (d << 24)) static unsigned hash(const char *cp) { unsigned rv = 0; int i, shift; if (!cp) return 0; for (i = 0, shift = 0; i < 4; ++i, shift += 8) { unsigned c = cp[i]; if (!c) break; c = tolower(c); rv |= (c << shift); } //if (i > 4) return 0; // .image is 5.... return rv; } const char *extname(const char *path) { const char *rv = NULL; if (!path) return path; for(unsigned i = 0; ; ++i) { unsigned c = path[i]; if (c == 0) break; if (c == '/') rv = NULL; if (c == '.') rv = path + i + 1; } if (rv && !*rv) rv = NULL; return rv; } /* * MAME cares deeply about file extensions. LIST NOT EXHAUSTIVE * * Hard Disk: .chd .hd .hdv .2mg .hdi * CD-ROM: .chd .cue .toc .nrg .gdi .iso .cdr * 5.25: .dsk .do .po .edd .woz .nib .mfi .dfi .rti * 3.5: .dc42 .woz .2mg .mfi .dfi .hfe .mfm .td0 .imd .d77 .d88 .1dd .cqm .cqi .dsk .ima .img .ufi .360 .ipf [also .po and .hdv] * Midi in: .mid * Midi out: .mid * Picture: .png * Cassette: .wav */ static bool is_raw_525(size_t disk_size) { if (disk_size & 255) return NO; size_t blocks = disk_size >> 8; return blocks == 560 || blocks == 640; } static BOOL is_raw_35(size_t disk_size) { if (disk_size & 511) return NO; size_t blocks = disk_size >> 9; // atari st has 360 (single sided) and 720 (double-sided) disks.. return blocks == 800 || blocks == 1600 || blocks == 1440 || blocks == 2880 || blocks == 720 || blocks == 360; } static MediaType is_woz(const uint8_t *buffer, size_t file_size) { /* woz 1/2 ? */ if (!memcmp(buffer, "WOZ1\xff\x0a\x0d\x0a", 8) || !memcmp(buffer, "WOZ2\xff\x0a\x0d\x0a", 8)) { if (!memcmp(buffer + 12, "INFO", 4)) { unsigned type = buffer[21]; // 1 = 5.25, 2 = 3.5 if (type == 1) return MediaType_5_25; if (type == 2) return MediaType_3_5; } return MediaTypeUnknown; } return MediaTypeError; } static MediaType is_dc42(const uint8_t *buffer, size_t file_size) { if (buffer[0x52] == 0x01 && buffer[0x53] == 0x00 && buffer[0] >= 1 && buffer[0] <= 0x3f) { int dsize = OSReadBigInt32(buffer, 0x40); int tsize = OSReadBigInt32(buffer, 0x44); if (dsize + tsize + 0x54 == file_size) { if (is_raw_35(dsize)) return MediaType_3_5; return MediaType_HardDisk; } } return MediaTypeError; } static MediaType is_2img(const uint8_t *buffer, size_t file_size) { if (!memcmp(buffer, "2IMG", 4)) { int format = OSReadLittleInt32(buffer, 0x0c); // 0 - dos order, 1 = prodos order, 2 = nib data int blocks = OSReadLittleInt32(buffer, 0x14); // prodos only. //int bytes = OSReadLittleInt32(buffer, 0x1c); if (format == 2 || format == 0) return MediaType_5_25; // nib and dos order if (is_raw_525(blocks * 512)) return MediaType_5_25; if (is_raw_35(blocks * 512)) return MediaType_3_5; return MediaType_HardDisk; // //return MediaTypeUnknown; } return MediaTypeError; } static MediaType is_chd(const uint8_t *buffer, size_t file_size) { if (!memcmp(buffer, "MComprHD", 8)) { static int offsets[] = { 0, 0, 0, 28, 28, 32}; // offset for logival bytes. int version = OSReadBigInt32(buffer, 12); if (version >= 3 && version <= 5) { long bytes = OSReadBigInt64(buffer, offsets[version]); if (is_raw_525(bytes)) return MediaType_5_25; if (is_raw_35(bytes)) return MediaType_3_5; return MediaType_HardDisk; // iso? } return MediaTypeUnknown; } return MediaTypeError; } static MediaType is_ipf(const uint8_t *buffer, size_t file_size) { // http://justsolve.archiveteam.org/wiki/IPF if (!memcmp(buffer, "CAPS\x00\x00\x00", 7)) return MediaType_3_5; return MediaTypeUnknown; } static MediaType is_hxcf(const uint8_t *buffer, size_t file_size) { // HxCFloppyEmulator if (!memcmp(buffer, "HXCMFM", 6)) return MediaType_3_5; return MediaTypeUnknown; } static MediaType is_msa(const uint8_t *buffer, size_t file_size) { // Atari MSA if (!memcmp(buffer, "\x0x\x0f", 2)) return MediaType_3_5; return MediaTypeUnknown; } static MediaType is_pasti(const uint8_t *buffer, size_t file_size) { // Atari STX/Pasti if (!memcmp(buffer, "RSY\x00\x03\x00", 6)) return MediaType_3_5; return MediaTypeUnknown; } MediaType ClassifyMediaFile(id file) { struct stat st; ssize_t size; unsigned char buffer[128]; int fd; const char *path = [file fileSystemRepresentation]; // or URL const char *ext = extname(path); unsigned ext_hash = hash(ext); memset(&st, 0, sizeof(st)); memset(buffer, 0, sizeof(buffer)); fd = open(path, O_RDONLY); if (fd < 0) return MediaTypeError; fstat(fd, &st); size = read(fd, buffer, sizeof(buffer)); close(fd); if (size <= 0) return MediaTypeError; // 13 sector support ? not on an event 512 block boundary. // = 116480 bytes. switch(ext_hash) { case _x3('w', 'o', 'z'): return is_woz(buffer, st.st_size); case _x3('2', 'm', 'g'): case _x4('2', 'i', 'm', 'g'): return is_2img(buffer, st.st_size); case _x3('c', 'h', 'd'): return is_chd(buffer, st.st_size); // dc42 - dc,dc42,dsk,img,image case _x2('d', 'c'): // n.b. as of .233, allowed for mac, not for apple2 case _x4('d', 'c', '4', '2'): return is_dc42(buffer, st.st_size); // dsk, image, img may also be raw or disk copy. case _x3('d', 's', 'k'): case _x3('i', 'm', 'g'): case _x4('i', 'm', 'a', 'g'): if (is_raw_525(st.st_size)) return MediaType_5_25; if (is_raw_35(st.st_size)) return MediaType_3_5; return is_dc42(buffer, st.st_size); case _x2('d', 'o'): case _x2('p', 'o'): if (is_raw_525(st.st_size)) return MediaType_5_25; if (is_raw_35(st.st_size)) return MediaType_3_5; return MediaTypeUnknown; // hdv - 3.5 or hard drive. case _x3('h', 'd', 'v'): case _x3('r', 'a', 'w'): if (is_raw_35(st.st_size)) return MediaType_3_5; if ((st.st_size & 511) == 0) return MediaType_HardDisk; return MediaTypeUnknown; case _x3('n', 'i', 'b'): return MediaType_5_25; case _x3('w', 'a', 'v'): return MediaType_Cassette; // cd-rom case _x3('i', 's', 'o'): case _x3('c', 'u', 'e'): case _x3('c', 'd', 'r'): return MediaType_CDROM; case _x3('p', 'n', 'g'): return MediaType_Picture; case _x3('m', 'i', 'd'): return MediaType_MIDI; // atari st, etc, 3.5 disk? // http://justsolve.archiveteam.org/wiki/Disk_Image_Formats case _x3('i', 'p', 'f'): return is_ipf(buffer, st.st_size); case _x3('m', 'f', 'm'): return is_hxcf(buffer, st.st_size); case _x3('m', 's', 'a'): return is_msa(buffer, st.st_size); case _x2('s', 't'): if (is_raw_35(st.st_size)) return MediaType_3_5; return MediaTypeUnknown; case _x3('s', 't', 'x'): // pasti return is_pasti(buffer, st.st_size); //case _x3('m', 'f', 'i'): // mame/mess floppy image [???] //case _x3('d', 'f', 'i'): // disc ferret image //case _x3('h', 'f', 'e'): // ??? //case _x3('t', 'd', '0'): // teledisk - may be split across multiple files (.td1, .td2, ...) //case _x3('i', 'm', 'd'): // imagedisk //case _x3('d', '7', '7'): //pc-88, may have multiple disk images //case _x3('d', '8', '8'): //fm-77, may have multiple disk images. //case _x3('1', 'd', 'd'): // related? //case _x3('c', 'q', 'm'): // copyQM //case _x3('c', 'q', 'i'): // copyQM case _x3('r', 'o', 'm'): case _x3('b', 'i', 'n'): return MediaType_ROM; } return MediaTypeUnknown; }