Xemu [doxygen]  hyppo 0a42be3a057156924bc1b626a687bd6e27349c45 @ Sat 19 Mar 02:15:11 CET 2022
d81access.c
Go to the documentation of this file.
1 /* Various D81 access method for F011 core, for Xemu / C65 and M65 emulators.
2  Part of the Xemu project, please visit: https://github.com/lgblgblgb/xemu
3  Copyright (C)2016-2022 LGB (Gábor Lénárt) <lgblgblgb@gmail.com>
4 
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9 
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
18 
19 #include "xemu/emutools.h"
20 #include "xemu/d81access.h"
21 #include "xemu/emutools_files.h"
22 
23 #include <sys/types.h>
24 #include <dirent.h>
25 #include <unistd.h>
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <limits.h>
29 
30 static struct {
31  int fd;
32  DIR *dir;
33  off_t start_at;
34  int mode;
36  //d81access_rd_cb_t read_cb;
37  //d81access_wr_cb_t write_cb;
38  // Only valid for PRG mode currently:
39  int prg_size;
42 } d81[8];
43 static int enable_mode_transient_callback = -1;
44 
45 // Note: D81_SIZE is defined in the header file, unlike these:
46 #define D64_SIZE 174848
47 #define D71_SIZE 349696
48 #define D65_SIZE 2785280
49 
50 #define IS_RO(p) (!!((p) & D81ACCESS_RO))
51 #define IS_RW(p) (!((p) & D81ACCESS_RO))
52 #define HAS_DISK(p) (((p)&& 0xFF) != D81ACCESS_EMPTY)
53 #define IS_AUTOCLOSE(p) (!!((p) & D81ACCESS_AUTOCLOSE))
54 
55 
56 void d81access_init ( void )
57 {
58  DEBUGPRINT("D81: initial subsystem reset" NL);
59  if (enable_mode_transient_callback != -1)
60  FATAL("d81access_init(): trying to re-run d81access_init()?!");
61  enable_mode_transient_callback = 1;
62  for (int i = 0; i < 8; i++) {
63  d81[i].fd = -1;
64  d81[i].dir = NULL;
65  d81[i].start_at = 0;
66  d81[i].mode = D81ACCESS_EMPTY;
67  d81access_cb_chgmode(i, d81[i].mode);
68  }
69 }
70 
71 
72 int d81access_get_mode ( int which )
73 {
74  return d81[which].mode;
75 }
76 
77 
78 void d81access_close ( int which )
79 {
80  if (d81[which].fd >= 0) {
81  if (IS_AUTOCLOSE(d81[which].mode)) {
82  close(d81[which].fd);
83  DEBUGPRINT("D81: previous file descriptor (%d) closed because of auto-close policy" NL, d81[which].fd);
84  } else
85  DEBUGPRINT("D81: previous file descriptor (%d) is NOT closed, because marked as non-autoclose!" NL, d81[which].fd);
86  d81[which].fd = -1;
87  }
88  if (d81[which].dir) {
89  closedir(d81[which].dir);
90  DEBUGPRINT("D81: previous directory access closed" NL);
91  d81[which].dir = NULL;
92  }
93  d81[which].mode = D81ACCESS_EMPTY;
94  d81[which].start_at = 0;
95  if (enable_mode_transient_callback)
96  d81access_cb_chgmode(which, d81[which].mode);
97 }
98 
99 
100 void d81access_close_all ( void )
101 {
102  for (int i = 0; i < 8; i++)
103  d81access_close(i);
104 }
105 
106 
107 static void d81access_close_internal ( int which )
108 {
109  enable_mode_transient_callback = 0;
110  d81access_close(which);
111  enable_mode_transient_callback = 1;
112 }
113 
114 
115 static void d81access_attach_fd_internal ( int which, int fd, off_t offset, int mode )
116 {
117  if (fd < 0)
118  FATAL("d81access_attach_fd_internal() tries to attach invalid fd");
119  d81access_close_internal(which);
120  if (HAS_DISK(mode)) {
121  d81[which].fd = fd;
122  d81[which].mode = mode;
123  d81[which].image_size = D81_SIZE; // the default size of the image ...
124  // ... override based on possible options, if image size is different:
125  if ((mode & D81ACCESS_D64))
126  d81[which].image_size = D64_SIZE;
127  if ((mode & D81ACCESS_D71))
128  d81[which].image_size = D71_SIZE;
129  if ((mode & D81ACCESS_D65))
130  d81[which].image_size = D65_SIZE;
131  DEBUGPRINT("D81: fd %d has been attached to #%d with " PRINTF_LLD " offset, read_only = %d, autoclose = %d, size = %d" NL, fd, which, (long long)offset, IS_RO(mode), IS_AUTOCLOSE(mode), d81[which].image_size);
132  } else {
133  DEBUGPRINT("D81: using empty access (no disk in drive) by request" NL);
134  d81[which].mode = D81ACCESS_EMPTY;
135  }
136  d81[which].start_at = offset;
137  d81access_cb_chgmode(which, d81[which].mode);
138 }
139 
140 
141 // this is used to attach external (to the d81access ...) file descriptor, not handled here to be opened as D81
142 // it's the caller's responsibility that it's really an FD for a D81 image in size etc enough for that!
143 // One example for this function to be used for: MEGA65, on-SDCARD "mounted" D81, where the "master" fd is used
144 // to access the D81 inside, managed by the caller!
145 void d81access_attach_fd ( int which, int fd, off_t offset, int mode )
146 {
147  int check_mode = mode & 0xFF;
148  if (check_mode != D81ACCESS_IMG && check_mode != D81ACCESS_EMPTY)
149  FATAL("d81access_attach_fd() mode low bits must have D81ACCESS_IMG or D81ACCESS_EMPTY");
150  d81access_attach_fd_internal(which, fd, offset, mode);
151 }
152 
153 
154 // FIXME: this API function is removed, since does not provide size information. Since currently it's not used,
155 // either remove it in the future, or refactor it!
156 #if 0
157 // Attach callbacks instead of handling requests in this source
158 void d81access_attach_cb ( int which, off_t offset, d81access_rd_cb_t rd_callback, d81access_wr_cb_t wr_callback )
159 {
160  d81access_close_internal(which);
161  d81[which].mode = D81ACCESS_CALLBACKS;
162  if (!wr_callback)
163  d81[which].mode |= D81ACCESS_RO;
164  d81[which].read_cb = rd_callback;
165  d81[which].write_cb = wr_callback;
166  d81[which].start_at = offset;
167  DEBUGPRINT("D81: attaching D81 via provided callbacks, read=%p, write=%p" NL, rd_callback, wr_callback);
168  d81access_cb_chgmode(which, d81[which].mode);
169 }
170 #endif
171 
172 
173 int d81access_attach_fsobj ( int which, const char *fn, int mode )
174 {
175  if (!fn || !*fn) {
176  DEBUGPRINT("D81: attach file request with empty file name, not using FS based disk attachment." NL);
177  return -1;
178  }
179  if (mode & D81ACCESS_DIR) {
180  // if we passed D81ACCESS_DIR, we try to open the named object as a directory first.
181  // if it was OK, let's containue with that
182  // if not OK and error was ENOTDIR or ENOENT then simply assume to continue with other methods (not as directory).
183  // this is because, the "fn" parameter can be a relative path too, later can be used with relative-to-preferences directory or so.
184  // though directory opening is always absolute path what we're assuming.
185  DIR *dir = opendir(fn);
186  if (dir) {
187  // It seems we could open the "raw" object as directory
188  d81access_close_internal(which);
189  d81[which].dir = dir;
190  d81[which].mode = D81ACCESS_DIR | D81ACCESS_RO | D81ACCESS_AUTOCLOSE; // TODO? directory access is always read only currently ...
191  d81[which].image_size = D81_SIZE;
192  DEBUGPRINT("D81: file system object \"%s\" opened as a directory." NL, fn);
193  d81access_cb_chgmode(which, d81[which].mode);
194  return 0;
195  } else if (errno != ENOTDIR && errno != ENOENT) {
196  ERROR_WINDOW("D81: cannot open directory %s for virtual D81 mode: %s", fn, strerror(errno));
197  return 1;
198  }
199  }
200  // So, we can assume that the object should be a file ...
201  if (!(mode & (D81ACCESS_IMG | D81ACCESS_PRG))) {
202  if (mode & D81ACCESS_DIR)
203  DEBUGPRINT("D81: could not open file system object \"%s\" as a directory.", fn);
204  else
205  FATAL("d81access_attach_fsobj(): insane mode argument, no DIR,IMG,PRG given");
206  return 1; // but if no success with directory open, and no request for file based attach, then we give up here
207  }
208  // OK, so some file based open request can happen, let's continue
209  char fnbuf[PATH_MAX + 1];
210  int ro = O_RDONLY;
211  int fd = xemu_open_file(fn, IS_RO(mode) ? O_RDONLY : O_RDWR, IS_RO(mode) ? NULL : &ro, fnbuf);
213  if (fd < 0) {
214  ERROR_WINDOW("D81: image/program file was specified (%s) but it cannot be opened: %s", fn, strerror(errno));
215  return 1;
216  }
218  if (size == OFF_T_ERROR) {
219  ERROR_WINDOW("D81: Cannot query the size of external D81 image/program file %s ERROR: %s", fn, strerror(errno));
220  close(fd);
221  return 1;
222  }
223  // Check if it's a PRG-file mode, based on the "sane" size of such a file ...
224  if (size >= PRG_MIN_SIZE && size <= PRG_MAX_SIZE) {
225  if (mode & D81ACCESS_PRG) {
226  d81access_attach_fd_internal(which, fd, 0, D81ACCESS_PRG | D81ACCESS_AUTOCLOSE | D81ACCESS_RO);
227  d81[which].prg_size = size; // store real size of the object
228  d81[which].prg_blk_size = size / 254;
229  d81[which].prg_blk_last_size = size % 254;
230  if (d81[which].prg_blk_last_size)
231  d81[which].prg_blk_size++;
232  else
233  d81[which].prg_blk_last_size = 254;
234  d81[which].image_size = D81_SIZE;
235  return 0;
236  } else {
237  close(fd);
238  ERROR_WINDOW("Specified size for D81 seems to be a program file (too small for real D81), but PRG mode virtual disk feature was not requested");
239  return 1;
240  }
241  }
242  if (!(mode & D81ACCESS_IMG)) {
243  close(fd);
244  ERROR_WINDOW("D81 image mode was not requested ...");
245  return 1;
246  }
247  // Fake-D64 ... If requested + allowed at all.
248  if (size == D64_SIZE && (mode & D81ACCESS_FAKE64)) {
249  // Set D81ACCESS_RO! As this is only a read-only hack mode!!
250  d81access_attach_fd_internal(which, fd, 0, D81ACCESS_IMG | D81ACCESS_AUTOCLOSE | D81ACCESS_FAKE64 | D81ACCESS_RO);
251  return 0;
252  }
253  // D64 mounted as a D81 - via ROM support
254  if (size == D64_SIZE && (mode & D81ACCESS_D64)) {
255  d81access_attach_fd_internal(which, fd, 0, D81ACCESS_IMG | D81ACCESS_AUTOCLOSE | D81ACCESS_D64 | ro);
256  return 0;
257  }
258  // D71 mounted as a D81 - via ROM support
259  if (size == D71_SIZE && (mode & D81ACCESS_D71)) {
260  d81access_attach_fd_internal(which, fd, 0, D81ACCESS_IMG | D81ACCESS_AUTOCLOSE | D81ACCESS_D71 | ro);
261  return 0;
262  }
263  // D65 mounted as a D81
264  if (size == D65_SIZE && (mode & D81ACCESS_D65)) {
265  d81access_attach_fd_internal(which, fd, 0, D81ACCESS_IMG | D81ACCESS_AUTOCLOSE | D81ACCESS_D65 | ro);
266  return 0;
267  }
268  // Only the possibility left that it's a D81 image
269  if (size == D81_SIZE) {
270  // candidate for the "normal" D81 as being used
271  d81access_attach_fd_internal(which, fd, 0, D81ACCESS_IMG | D81ACCESS_AUTOCLOSE | ro);
272  return 0;
273  }
274  close(fd);
275  ERROR_WINDOW("Cannot guess the type of object (from its size) wanted to use for floppy emulation");
276  return 1;
277 }
278 
279 
280 static int file_io_op ( const int which, const int is_write, const int image_offset, Uint8 *buffer, const int size )
281 {
282  off_t offset = d81[which].start_at + (off_t)image_offset;
283  if (lseek(d81[which].fd, offset, SEEK_SET) != offset)
284  FATAL("D81: SEEK: seek host-OS failure: %s", strerror(errno));
285  const int ret = is_write ? xemu_safe_write(d81[which].fd, buffer, size) : xemu_safe_read(d81[which].fd, buffer, size);
286  if (ret >= 0)
287  return ret;
288  FATAL("D81: %s: host-OS error: %s", is_write ? "WRITE" : "READ", strerror(errno));
289  return -1;
290 }
291 
292 
293 static int read_fake64 ( const int which, Uint8 *buffer, int d81_offset, int number_of_logical_sectors )
294 {
295  for (; number_of_logical_sectors; number_of_logical_sectors--, d81_offset += 0x100, buffer += 0x100) {
296  int track = d81_offset / 0x2800 + 1; // Calculate D81 requested track number (starting from 1)
297  int sector = (d81_offset % 0x2800) >> 8;// Calculate D81 requested sector number, in _256_ bytes long sectors though! (starting from 0)
298  if (track == 18) {
299  memset(buffer, 0, 0x100);
300  DEBUGPRINT("D81: FAKE64: D81 track 18 tried to be read, which would be the D64 dir/sys track. Ignoring!" NL);
301  continue;
302  }
303  if (track == 40) // track 40 on D81 is the directory. We replace that with track 18 on the D64 (some workarounds still needed to be applied later, possibly)
304  track = 18;
305  if (!track || track > 35) { // not existing track on D64
306  memset(buffer, 0, 0x100);
307  DEBUGPRINT("D81: FAKE64: invalid track for D64 %d" NL, track);
308  continue;
309  }
310  // Resolve number of sectors on D64 given by the track (unlike D81, it's not a constant!)
311  // Also work out the base byte offset of the track in the D64 image ("sector * 256" should be added later)
312  int d64_max_sectors, d64_track_ofs;
313  if (track <= 17) { // D64 tracks 1-17
314  d64_track_ofs = 21 * 256 * (track - 1) + 0x00000;
315  d64_max_sectors = 21;
316  } else if (track <= 24) { // D64 tracks 18-24
317  d64_track_ofs = 19 * 256 * (track - 18) + 0x16500;
318  d64_max_sectors = 19;
319  } else if (track <= 30) { // D64 tracks 25-30
320  d64_track_ofs = 18 * 256 * (track - 25) + 0x1EA00;
321  d64_max_sectors = 18;
322  } else { // D64 tracks 31-35
323  d64_track_ofs = 17 * 256 * (track - 31) + 0x25600;
324  d64_max_sectors = 17;
325  }
326  if (sector >= d64_max_sectors) { // requested sector does not exist on D64 on the given track
327  memset(buffer, 0, 0x100);
328  DEBUGPRINT("D81: FAKE64: invalid sector for D64 %d on track %d" NL, sector, track);
329  continue;
330  }
331  // This is now checks for 18, as we already translated our 40 to 18 as the directory track!!
332  int sector_to_read;
333  if (track == 18) {
334  if (sector >= 3)
335  sector_to_read = sector - 2;
336  else
337  sector_to_read = 0; // for D81 sectors 0,1,2 we always want to read sector 0 of D64 to extract the needed information from there!
338  } else
339  sector_to_read = sector;
340  if (track == 18 || sector_to_read != sector)
341  DEBUGPRINT("D81: FAKE64: translated to D64 track:sector %d:%d from the orginal requested %d:%d" NL, track, sector_to_read, track == 18 ? 40 : track, sector);
342  if (file_io_op(which, 0, d64_track_ofs + sector_to_read * 256, buffer, 0x100) != 0x100) {
343  DEBUGPRINT("D81: FAKE64: read failed!" NL);
344  return -1;
345  }
346  // This is now checks for 18, as we already translated our 40 to 18 as the directory track!!
347  if (track == 18) {
348  if (sector == 0) {
349  buffer[0] = 40; // next track
350  buffer[1] = 3; // next sector
351  buffer[2] = 0x44; // 'D': DOS version, this is for 1581
352  buffer[3] = 0;
353  memcpy(buffer + 4, buffer + 0x90, 16); // 0x4 - 0x13: disk name, on D64 that is from offset $90
354  buffer[0x14] = 0xA0;
355  buffer[0x15] = 0xA0;
356  buffer[0x16] = buffer[0xA2]; // disk ID
357  buffer[0x17] = buffer[0xA3]; // disk ID
358  buffer[0x18] = 0xA0;
359  buffer[0x19] = 0x33; // DOS version: '3'
360  buffer[0x1A] = 0x44; // DOS version: 'D'
361  buffer[0x1B] = 0xA0;
362  buffer[0x1C] = 0xA0;
363  memset(buffer + 0x1D, 0, 0x100 - 0x1D);
364  } else if (sector == 1 || sector == 2) {
365  // these two sectors are the BAM on D81 among some other information (partly the same as with sector 0)
366  Uint8 obuffer[0x100];
367  memcpy(obuffer, buffer, 0x100); // save these, as we overwrite the original values ...
368  memset(buffer, 0, 0x100);
369  if (sector == 1) { // sector 1, first BAM
370  buffer[0] = 40;
371  buffer[1] = 2;
372  for (int tf0 = 0; tf0 < 35; tf0++) {
373  Uint8 *obam = obuffer + 4 + (4 * tf0);
374  Uint8 *nbam = buffer + 0x10 + (6 * tf0);
375  if (tf0 + 1 != 18) // tf0 -> "track from zero" (non std method), skip the directory/system track (memset above already did the trick)
376  memcpy(nbam, obam, 4);
377  }
378  } else { // sector 2, second BAM (not used, as all the D64 tracks fits into the first part of the BAM ...)
379  buffer[0] = 0x00;
380  buffer[1] = 0xFF;
381  }
382  buffer[2] = 0x44; // 'D': DOS version
383  buffer[3] = 0xBB; // one's complement of the version ...
384  buffer[4] = obuffer[0xA2]; // disk ID, same as in sector 0
385  buffer[5] = obuffer[0xA3]; // disk ID, same as in sector 0
386  buffer[6] = 0xC0; // flags [it seems it's usually $C0?]
387  buffer[7] = 0; // autoboot?
388  } else if (buffer[0] == 18) { // fix chain track/sector in the buffer to have some meaningful values for D81 context on track 40 (vs track 18 on D64)
389  buffer[0] = 40;
390  if (buffer[1] > 0 && buffer[1] < 19)
391  buffer[1] += 2;
392  }
393  }
394  }
395  return 0;
396 }
397 
398 
399 static int read_prg ( const int which, Uint8 *buffer, int d81_offset, int number_of_logical_sectors )
400 {
401  static const Uint8 vdsk_head_sect[] = {
402  0x28, 0x03,
403  0x44, 0x00,
404  'X', 'E', 'M', 'U', ' ', 'V', 'R', '-', 'D', 'I', 'S', 'K', ' ', 'R', '/', 'O',
405  0xA0, 0xA0,
406  '6', '5',
407  0xA0,
408  0x33, 0x44, 0xA0, 0xA0
409  };
410  static const Uint8 vdsk_file_name[16] = {
411  'F', 'I', 'L', 'E', 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0
412  };
413  // just pre-zero buffer, so we don't need to take care on this at various code points with possible partly filled output
414  memset(buffer, 0, 512);
415  // disk organization at CBM-DOS level is 256 byte sector based, though FDC F011 itself is 512 bytes sectored stuff
416  // so we always need to check to 256 bytes "DOS-evel" sectors even if F011 itself handled 512 bytes long sectors
417  for (; number_of_logical_sectors; number_of_logical_sectors--, d81_offset += 0x100, buffer += 0x100) {
418  DEBUGPRINT("D81VIRTUAL: reading sub-sector @ $%X" NL, d81_offset);
419  if (d81_offset == 0x61800) { // the header sector
420  memcpy(buffer, vdsk_head_sect, sizeof vdsk_head_sect);
421  } else if (d81_offset == 0x61900 || d81_offset == 0x61A00) { // BAM sectors (we don't handle BAM entries at all, so it will be a filled disk ...)
422  if (d81_offset == 0x61900) {
423  buffer[0] = 0x28;
424  buffer[1] = 0x02;
425  } else
426  buffer[1] = 0xFF; // chain, byte #0 is already 0
427  buffer[2] = 0x44;
428  buffer[3] = 0xBB;
429  buffer[4] = vdsk_head_sect[0x16];
430  buffer[5] = vdsk_head_sect[0x17];
431  buffer[6] = 0xC0;
432  } else if (d81_offset == 0x61B00) { // directory sector, the only one we want to handle here
433  buffer[2] = 0x82; // PRG
434  buffer[3] = 0x01; // starts on track-1
435  // starts on sector-0 of track-1, 0 is already set
436  memcpy(buffer + 5, vdsk_file_name, 16);
437  buffer[0x1E] = d81[which].prg_blk_size & 0xFF;
438  buffer[0x1F] = d81[which].prg_blk_size >> 8;
439  memcpy(buffer + 0x22, buffer + 2, 0x20 - 2); // the next dir entry is the same, BUT:
440  buffer[0x22] = 0x81; // ... SEQ prg type
441  buffer[0x29] = 'S'; // ... "SEQ" after name "FILE", so we have "FILE" (PRG) and "FILESEQ" (SEQ)
442  buffer[0x2A] = 'E';
443  buffer[0x2B] = 'Q';
444  } else { // what we want to handle at all yet, is the file itself, which starts at the very beginning at our 'virtual' disk
445  int block = d81_offset >> 8; // calculate the block from offset
446  if (block < d81[which].prg_blk_size) { // so it seems, we need to do something here at last, disk area belongs to our file!
447  int reqsize, ret;
448  if (block == d81[which].prg_blk_size -1) { // last block of file
449  reqsize = d81[which].prg_blk_last_size;
450  buffer[1] = 0xFF; // offs 0 is already 0
451  } else { // not the last block, we must resolve the track/sector info of the next block
452  reqsize = 254;
453  buffer[0] = ((block + 1) / 40) + 1;
454  buffer[1] = (block + 1) % 40;
455  }
456  ret = file_io_op(which, 0, block * 254, buffer + 2, reqsize);
457  DEBUGPRINT("D81VIRTUAL: ... data block, block number %d, next_track = $%02X next_sector = $%02X" NL, block, buffer[0], buffer[1]);
458 #if 0
459  if (host_seek_to(NULL, block * 254, "reading[PRG81VIRT@HOST]", d81_is_prg + 512, d81fd) < 0)
460  return -1;
461  block = xemu_safe_read(d81fd, buffer + 2, reqsize);
462 #endif
463  DEBUGPRINT("D81VIRTUAL: ... reading result: expected %d retval %d" NL, reqsize, ret);
464  if (ret != reqsize)
465  return -1;
466  } // if it's not our block of the file, not BAMs, header block or directory, the default zeroed area is returned, what we memset()'ed to zero
467  }
468  }
469  return 0;
470 }
471 
472 
473 // Notes:
474 // * all calculations are based on 512 bytes sector, "sector_size" parameter is merely is a read size not the actual size of the sector
475 // * sectors per track meant as on one side only!
476 
477 static int check_io_req_params ( const int which, const Uint8 side, const Uint8 track, const Uint8 sector, const int sector_size, int *io_size_p )
478 {
479  if (XEMU_UNLIKELY(sector_size != 0x100 && sector_size != 0x200))
480  FATAL("D81ACCESS: check_io_req_params(): invalid sector size %d", sector_size);
481  if (XEMU_UNLIKELY(sector == 0)) {
482  DEBUGPRINT("D81ACCESS: warning, trying file op on sector-0 within track %d" NL, track);
483  return -1;
484  }
485  int offset; //, num_of_sides, num_of_tracks, num_of_sectors;
486  // NOTE: MEGA65 D71 and D64 access works as leaving D81 calculation as-is, and from software you need to do the geometry conversion, so not the scope of the emulator
487  // (this also means that a D64/D71 can be addressed beyond the end of the disk image, it needs a final check on offset not only geometry info limitations!)
488  // However, D65 has its own geometry calulcation at hardware level!
489  if (XEMU_UNLIKELY(d81[which].mode & D81ACCESS_D65)) {
490  // FIXME: no idea about the exact values for D65, or the formula :(
491  offset = (((const int)track << 7) + (sector - 1)) * 512;
492  //num_of_sides = 2;
493  //num_of_tracks = 85;
494  //num_of_sectors = 64; // (sectors by SIDE ...)
495  } else {
496  offset = 40 * (track - 0) * 256 + (sector - 1) * 512 + side * 20 * 256; // somewhat experimental value, it was after I figured out how that should work :-P
497  //num_of_sides = 2;
498  //num_of_tracks = 80;
499  //num_of_sectors = 10; // (sectors by SIDE ...)
500  }
501  // FIXME: I have no idea why it does not work. It seems, some software wants to access sector 17 and such,
502  // which does not exist (only 10 sectors per side). By deactivating these checks, everything seems to work again :-O
503 #if 0
504  // Check disk geometry limit (note about the D64/D71 comment above, those are handled internally as D81 disk geometry!)
505  if (XEMU_UNLIKELY(num_of_sides != -1 && side >= num_of_sides)) { // num_of_sides = -1 --> do not check side info
506  DEBUGPRINT("D81ACCESS: trying to access non-existing side (%d)" NL, side);
507  return -1;
508  }
509  if (XEMU_UNLIKELY(track >= num_of_tracks)) {
510  DEBUGPRINT("D81ACCESS: trying to access non-existing track (%d)" NL, track);
511  return -1;
512  }
513  if (XEMU_UNLIKELY(sector > num_of_sectors || sector == 0)) { // sector numbering starts with ONE, not with zero!
514  DEBUGPRINT("D81ACCESS: trying to access non-exisiting sector (%d) on track %d" NL, sector, track);
515  return -1;
516  }
517 #endif
518  // Check offset limit
519  if (XEMU_UNLIKELY(offset < 0 || offset >= d81[which].image_size)) {
520  DEBUGPRINT("D81ACCESS: trying to R/W beyond the end of disk image (offset %d, size %d)" NL, offset, d81[which].image_size);
521  return -1;
522  }
523  // Check "partial" I/O, can happen with a disk image like D64 is accessed as a D81 and D64 image size is not divisable by 512, only by 256
524  const int io_size = d81[which].image_size - offset;
525  if (XEMU_UNLIKELY(io_size < sector_size)) {
526  DEBUGPRINT("D81ACCESS: partial R/W with %d bytes" NL, io_size);
527  if (io_size != 256) {
528  // this should not happen though ...
529  ERROR_WINDOW("Partial R/W on D81 access which is not 256 byte in length but %d (at offset %d, image size is %d bytes)!", io_size, offset, d81[which].image_size);
530  return -1;
531  }
532  *io_size_p = io_size;
533  } else
534  *io_size_p = sector_size;
535  return offset;
536 }
537 
538 
539 int d81access_read_sect ( const int which, Uint8 *buffer, const Uint8 side, const Uint8 track, const Uint8 sector, const int sector_size )
540 {
541  int io_size;
542  const int offset = check_io_req_params(which, side, track, sector, sector_size, &io_size);
543  if (XEMU_UNLIKELY(offset < 0))
544  return offset; // return negative number as error
545  switch (d81[which].mode & 0xFF) {
546  case D81ACCESS_EMPTY:
547  return -1;
548  case D81ACCESS_IMG:
549  if (XEMU_UNLIKELY(d81[which].mode & D81ACCESS_FAKE64)) {
550  return read_fake64(which, buffer, offset, sector_size >> 8);
551  } else {
552  // we fill the buffer if partial read is done to have consistent "tail"
553  if (XEMU_UNLIKELY(io_size != sector_size))
554  memset(buffer, 0xFF, sector_size);
555  return file_io_op(which, 0, offset, buffer, io_size) == io_size ? 0 : -1;
556  }
557  case D81ACCESS_PRG:
558  return read_prg(which, buffer, offset, sector_size >> 8);
559  case D81ACCESS_DIR:
560  FATAL("D81ACCESS: DIR access method is not yet implemented in Xemu, sorry :-(");
561  case D81ACCESS_CALLBACKS:
562  FATAL("D81: D81ACCESS_CALLBACKS is not implemented!");
563  //return d81[which].read_cb(which, buffer, d81[which].start_at + offset, sector_size);
564  default:
565  FATAL("D81ACCESS: d81access_read_sect(): invalid value for 'd81[%d].mode & 0xFF' = %d", which, d81[which].mode & 0xFF);
566  }
567  FATAL("D81ACCESS: d81access_read_sect() unhandled case" NL);
568  return -1;
569 }
570 
571 
572 int d81access_write_sect ( const int which, Uint8 *buffer, const Uint8 side, const Uint8 track, const Uint8 sector, const int sector_size )
573 {
574  int io_size;
575  const int offset = check_io_req_params(which, side, track, sector, sector_size, &io_size);
576  if (IS_RO(d81[which].mode))
577  return -1;
578  if (XEMU_UNLIKELY(offset < 0))
579  return offset; // return negative number as error
580  switch (d81[which].mode & 0xFF) {
581  case D81ACCESS_EMPTY:
582  return -1;
583  case D81ACCESS_IMG:
584  return file_io_op(which, 1, offset, buffer, io_size) == io_size ? 0 : -1;
585  case D81ACCESS_PRG:
586  case D81ACCESS_DIR:
587  return -1; // currently, these are all read-only, even if caller forgets that and try :-O
588  case D81ACCESS_CALLBACKS:
589  FATAL("D81: D81ACCESS_CALLBACKS is not implemented!");
590  //return (d81[which].write_cb ? d81[which].write_cb(which, buffer, d81[which].start_at + offset, sector_size) : -1);
591  default:
592  FATAL("D81ACCESS: d81access_write_sect(): invalid d81[%d].mode & 0xFF", which);
593  }
594  FATAL("D81ACCESS: d81access_write_sect() unhandled case" NL);
595  return -1;
596 }
597 
598 
599 // Notes:
600 // * if img is NULL, disk image is malloc()'ed and the caller should free() the space
601 // * return value is the actual image, either the same as img as input, or the malloc()'ed address if img was NULL
602 // * if name_from_fn is non-zero, then this function tries some heuristics to form a "sane" disk name from a win/unix/etc file path given
603 // otherwise diskname is used as-is (other than converting case, etc)
604 Uint8 *d81access_create_image ( Uint8 *img, const char *diskname, const int name_from_fn )
605 {
606  if (!img)
607  img = xemu_malloc(D81_SIZE);
608  static const char diskname_default[] = "XEMU-NAMELESS";
609  if (!diskname)
610  diskname = diskname_default;
611  static const Uint8 mods_at_61800[] = {
612 #if 0
613  /* 61800 */ 0x28,0x03,0x44,0x00,0x44,0x45,0x4d,0x4f,0x45,0x4d,0x50,0x54,0x59,0xa0,0xa0,0xa0, /* | (.D.DEMOEMPTY...| */
614 #endif
615  /* 61800 */ 0x28,0x03,0x44,0x00,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0, /* | (.D.............| */
616  /* 61810 */ 0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0x30,0x30,0xa0,0x33,0x44,0xa0,0xa0 /* | ......00.3D.....| */
617  };
618  static const Uint8 mods_at_61900[] = {
619  /* 61900 */ 0x28,0x02,0x44,0xbb,0x30,0x30,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* | (.D.00..........| */
620  /* 61910 */ 0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff, /* | (.....(.....(...| */
621  /* 61920 */ 0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff, /* | ..(.....(.....(.| */
622  /* 61930 */ 0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff, /* | ....(.....(.....| */
623  /* 61940 */ 0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff, /* | (.....(.....(...| */
624  /* 61950 */ 0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff, /* | ..(.....(.....(.| */
625  /* 61960 */ 0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff, /* | ....(.....(.....| */
626  /* 61970 */ 0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff, /* | (.....(.....(...| */
627  /* 61980 */ 0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff, /* | ..(.....(.....(.| */
628  /* 61990 */ 0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff, /* | ....(.....(.....| */
629  /* 619a0 */ 0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff, /* | (.....(.....(...| */
630  /* 619b0 */ 0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff, /* | ..(.....(.....(.| */
631  /* 619c0 */ 0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff, /* | ....(.....(.....| */
632  /* 619d0 */ 0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff, /* | (.....(.....(...| */
633  /* 619e0 */ 0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff, /* | ..(.....(.....(.| */
634  /* 619f0 */ 0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x24,0xf0,0xff,0xff,0xff,0xff, /* | ....(.....$.....| */
635  /* 61a00 */ 0x00,0xff,0x44,0xbb,0x30,0x30,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* | ..D.00..........| */
636  /* 61a10 */ 0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff, /* | (.....(.....(...| */
637  /* 61a20 */ 0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff, /* | ..(.....(.....(.| */
638  /* 61a30 */ 0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff, /* | ....(.....(.....| */
639  /* 61a40 */ 0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff, /* | (.....(.....(...| */
640  /* 61a50 */ 0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff, /* | ..(.....(.....(.| */
641  /* 61a60 */ 0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff, /* | ....(.....(.....| */
642  /* 61a70 */ 0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff, /* | (.....(.....(...| */
643  /* 61a80 */ 0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff, /* | ..(.....(.....(.| */
644  /* 61a90 */ 0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff, /* | ....(.....(.....| */
645  /* 61aa0 */ 0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff, /* | (.....(.....(...| */
646  /* 61ab0 */ 0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff, /* | ..(.....(.....(.| */
647  /* 61ac0 */ 0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff, /* | ....(.....(.....| */
648  /* 61ad0 */ 0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff, /* | (.....(.....(...| */
649  /* 61ae0 */ 0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff, /* | ..(.....(.....(.| */
650  /* 61af0 */ 0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff,0x28,0xff,0xff,0xff,0xff,0xff, /* | ....(.....(.....| */
651  /* 61b00 */ 0x00,0xff /* | ................| */
652  };
653  memset(img, 0, D81_SIZE);
654  memcpy(img + 0x61800, mods_at_61800, sizeof mods_at_61800);
655  memcpy(img + 0x61900, mods_at_61900, sizeof mods_at_61900);
656  int namelen;
657  if (name_from_fn) {
658  // in this mode, we want to generate diskname from a filename, so additional rules should be applied
659  if (diskname[0] == '@' || diskname[0] == '#')
660  diskname++; // skip first char if '@' or '#' since it's used by Xemu to signal pref/base dir expansion
661  const char *r = strrchr(diskname, DIRSEP_CHR);
662  if (r)
663  diskname = r + 1; // skip the path part, and use the filename part only, if dirsep character is found at least
664  namelen = strlen(diskname);
665  if (namelen > 4 && !strcasecmp(diskname + namelen - 4, ".D81"))
666  namelen -= 4; // do not use the ".D81" part at the end in the disk name
667  } else
668  namelen = strlen(diskname);
669  if (namelen > 16) {
670  namelen = 16;
671  } else if (!namelen) {
672  diskname = diskname_default;
673  namelen = strlen(diskname_default);
674  }
675  unsigned int diskid = namelen + ((namelen + 1) << 8);
676  DEBUGPRINT("D81ACCESS: creating memory image of a new D81 disk \"");
677  for (unsigned int i = 0; i < namelen; i++) {
678  Uint8 c = (Uint8)diskname[i];
679  diskid += (unsigned int)c + (i << 5);
680  if (c >= 'a' && c <= 'z')
681  c -= 32;
682  else if (c < 32 || c >= 0x7F)
683  c = '?';
684  img[0x61804 + i] = c;
685  DEBUGPRINT("%c", c);
686  }
687  diskid = (diskid ^ (diskid >> 5));
688  for (unsigned int i = 0; i < 2; i++, diskid /= 36) {
689  const Uint8 c = diskid % 36;
690  img[0x61816 + i] = c < 26 ? c + 'A' : c - 26 + '0';
691  }
692  DEBUGPRINT("\",\"%c%c\"" NL, img[0x61816], img[0x61817]);
693  return img;
694 }
695 
696 
697 // Return values:
698 // 0 = OK, image created
699 // -1 = some error
700 // -2 = file existed before, and do_overwrite as not specified
701 int d81access_create_image_file ( const char *fn, const char *diskname, const int do_overwrite, const char *cry )
702 {
703  char fullpath[PATH_MAX + 1];
704  const int fd = xemu_open_file(fn, O_WRONLY | O_CREAT | O_TRUNC | (!do_overwrite ? O_EXCL : 0), NULL, fullpath);
705  if (fd < 0) {
706  const int ret = (errno == EEXIST) ? -2 : -1;
707  if (cry)
708  ERROR_WINDOW("%s [D81-CREATE]\n%s\n%s", cry, fn, strerror(errno));
709  if (ret == -2)
710  DEBUGPRINT("D81ACCESS: D81 image \"%s\" existed before." NL, fn);
711  return ret;
712  }
713  Uint8 *img = d81access_create_image(NULL, diskname ? diskname : fn, !diskname);
714  const int written = xemu_safe_write(fd, img, D81_SIZE);
715  xemu_os_close(fd);
716  free(img);
717  if (written != D81_SIZE) {
718  if (cry)
719  ERROR_WINDOW("%s [D81-WRITE]\n%s\n%s", cry, fullpath, strerror(errno));
720  xemu_os_unlink(fullpath);
721  return -1;
722  }
723  DEBUGPRINT("D81ACCESS: new disk image file \"%s\" has been successfully created (overwrite policy %d)." NL, fullpath, do_overwrite);
724  return 0;
725 }
d81access_close_all
void d81access_close_all(void)
Definition: d81access.c:100
D71_SIZE
#define D71_SIZE
Definition: d81access.c:47
HAS_DISK
#define HAS_DISK(p)
Definition: d81access.c:52
D81ACCESS_AUTOCLOSE
#define D81ACCESS_AUTOCLOSE
Definition: d81access.h:38
emutools.h
D81ACCESS_D71
#define D81ACCESS_D71
Definition: d81access.h:41
d81access_read_sect
int d81access_read_sect(const int which, Uint8 *buffer, const Uint8 side, const Uint8 track, const Uint8 sector, const int sector_size)
Definition: d81access.c:539
d81access_write_sect
int d81access_write_sect(const int which, Uint8 *buffer, const Uint8 side, const Uint8 track, const Uint8 sector, const int sector_size)
Definition: d81access.c:572
xemu_open_file
int xemu_open_file(const char *filename, int mode, int *mode2, char *filepath_back)
Definition: emutools_files.c:469
OFF_T_ERROR
#define OFF_T_ERROR
Definition: fat32.c:42
fd
int fd
Definition: d81access.c:31
prg_blk_last_size
int prg_blk_last_size
Definition: d81access.c:41
d81access_init
void d81access_init(void)
Definition: d81access.c:56
fn
const char * fn
Definition: roms.c:42
IS_AUTOCLOSE
#define IS_AUTOCLOSE(p)
Definition: d81access.c:53
mode
int mode
Definition: d81access.c:34
d81access_attach_fsobj
int d81access_attach_fsobj(int which, const char *fn, int mode)
Definition: d81access.c:173
Uint8
uint8_t Uint8
Definition: fat32.c:51
d81access_get_mode
int d81access_get_mode(int which)
Definition: d81access.c:72
D64_SIZE
#define D64_SIZE
Definition: d81access.c:46
D81ACCESS_D65
#define D81ACCESS_D65
Definition: d81access.h:42
prg_size
int prg_size
Definition: d81access.c:39
D81_SIZE
#define D81_SIZE
Definition: d81access.h:22
block
Uint32 block
Definition: fat32.c:156
xemu_safe_file_size_by_fd
off_t xemu_safe_file_size_by_fd(int fd)
Definition: emutools_files.c:583
emutools_files.h
xemu_malloc
void * xemu_malloc(size_t size)
Definition: emutools.c:226
xemu_os_close
#define xemu_os_close
Definition: emutools.h:272
DEBUGPRINT
#define DEBUGPRINT(...)
Definition: emutools_basicdefs.h:171
ro
int ro
Definition: cpmfs.c:48
PRG_MAX_SIZE
#define PRG_MAX_SIZE
Definition: d81access.h:29
D81ACCESS_D64
#define D81ACCESS_D64
Definition: d81access.h:40
d81access_close
void d81access_close(int which)
Definition: d81access.c:78
D81ACCESS_CALLBACKS
#define D81ACCESS_CALLBACKS
Definition: d81access.h:36
D81ACCESS_RO
#define D81ACCESS_RO
Definition: d81access.h:37
d81access_create_image
Uint8 * d81access_create_image(Uint8 *img, const char *diskname, const int name_from_fn)
Definition: d81access.c:604
ERROR_WINDOW
#define ERROR_WINDOW(...)
Definition: xep128.h:116
d81access_attach_fd
void d81access_attach_fd(int which, int fd, off_t offset, int mode)
Definition: d81access.c:145
d81access_cb_chgmode
void d81access_cb_chgmode(const int which, const int mode)
Definition: commodore_65.c:282
d81access_create_image_file
int d81access_create_image_file(const char *fn, const char *diskname, const int do_overwrite, const char *cry)
Definition: d81access.c:701
D81ACCESS_PRG
#define D81ACCESS_PRG
Definition: d81access.h:34
D81ACCESS_EMPTY
#define D81ACCESS_EMPTY
Definition: d81access.h:32
NL
#define NL
Definition: fat32.c:37
compress_sd_image.r
def r
Definition: compress_sd_image.py:76
prg_blk_size
int prg_blk_size
Definition: d81access.c:40
D81ACCESS_FAKE64
#define D81ACCESS_FAKE64
Definition: d81access.h:39
PRINTF_LLD
#define PRINTF_LLD
Definition: emutools_basicdefs.h:145
D65_SIZE
#define D65_SIZE
Definition: d81access.c:48
xemu_os_unlink
#define xemu_os_unlink
Definition: emutools.h:266
image_size
int image_size
Definition: d81access.c:35
xemu_safe_read
ssize_t xemu_safe_read(int fd, void *buffer, size_t length)
Definition: emutools_files.c:543
size
int size
Definition: inject.c:37
dir
DIR * dir
Definition: d81access.c:32
IS_RO
#define IS_RO(p)
Definition: d81access.c:50
start_at
off_t start_at
Definition: d81access.c:33
D81ACCESS_DIR
#define D81ACCESS_DIR
Definition: d81access.h:35
D81ACCESS_IMG
#define D81ACCESS_IMG
Definition: d81access.h:33
xemu_safe_write
ssize_t xemu_safe_write(int fd, const void *buffer, size_t length)
Definition: emutools_files.c:563
FATAL
#define FATAL(...)
Definition: xep128.h:117
XEMU_OPEN_FILE_FIRST_MODE_USED
#define XEMU_OPEN_FILE_FIRST_MODE_USED
Definition: emutools_files.h:45
XEMU_UNLIKELY
#define XEMU_UNLIKELY(__x__)
Definition: emutools_basicdefs.h:125
d81access.h
PRG_MIN_SIZE
#define PRG_MIN_SIZE
Definition: d81access.h:24
DIRSEP_CHR
#define DIRSEP_CHR
Definition: emutools_basicdefs.h:142