diff --git a/src/cdrom/CMakeLists.txt b/src/cdrom/CMakeLists.txt index ecd0d934e..347a0e19d 100644 --- a/src/cdrom/CMakeLists.txt +++ b/src/cdrom/CMakeLists.txt @@ -13,4 +13,4 @@ # Copyright 2020,2021 David Hrdlička. # -add_library(cdrom OBJECT cdrom.c cdrom_image_backend.c cdrom_image.c) +add_library(cdrom OBJECT cdrom.c cdrom_image_backend.c cdrom_image_viso.c cdrom_image.c) diff --git a/src/cdrom/cdrom_image.c b/src/cdrom/cdrom_image.c index a4023734e..b777bcdee 100644 --- a/src/cdrom/cdrom_image.c +++ b/src/cdrom/cdrom_image.c @@ -285,11 +285,12 @@ cdrom_image_open(cdrom_t *dev, const char *fn) dev->image = img; /* Open the image. */ - if (!cdi_set_device(img, fn)) + int i = cdi_set_device(img, fn); + if (!i) return image_open_abort(dev); /* All good, reset state. */ - if (! strcasecmp(plat_get_extension((char *) fn), "ISO")) + if (i == 2) dev->cd_status = CD_STATUS_DATA_ONLY; else dev->cd_status = CD_STATUS_STOPPED; diff --git a/src/cdrom/cdrom_image_backend.c b/src/cdrom/cdrom_image_backend.c index 2542da63c..c8d1c3570 100644 --- a/src/cdrom/cdrom_image_backend.c +++ b/src/cdrom/cdrom_image_backend.c @@ -1,58 +1,53 @@ /* - * 86Box A hypervisor and IBM PC system emulator that specializes in - * running old operating systems and software designed for IBM - * PC systems and compatibles from 1981 through fairly recent - * system designs based on the PCI bus. + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. * - * This file is part of the 86Box distribution. + * This file is part of the 86Box distribution. * - * CD-ROM image file handling module, translated to C from - * cdrom_dosbox.cpp. + * CD-ROM image file handling module, translated to C from + * cdrom_dosbox.cpp. * - * Authors: Miran Grca, - * Fred N. van Kempen, - * The DOSBox Team, + * Authors: Miran Grca, + * Fred N. van Kempen, + * The DOSBox Team, * - * Copyright 2016-2020 Miran Grca. - * Copyright 2017-2020 Fred N. van Kempen. - * Copyright 2002-2020 The DOSBox Team. + * Copyright 2016-2020 Miran Grca. + * Copyright 2017-2020 Fred N. van Kempen. + * Copyright 2002-2020 The DOSBox Team. */ #define _LARGEFILE_SOURCE #define _LARGEFILE64_SOURCE #define __STDC_FORMAT_MACROS -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include #ifdef _WIN32 -# include +# include #else -# include +# include #endif -#include #define HAVE_STDARG_H #include <86box/86box.h> -#include <86box/plat.h> #include <86box/cdrom_image_backend.h> +#include <86box/plat.h> +#define CDROM_BCD(x) (((x) % 10) | (((x) / 10) << 4)) -#define CDROM_BCD(x) (((x) % 10) | (((x) / 10) << 4)) - -#define MAX_LINE_LENGTH 512 -#define MAX_FILENAME_LENGTH 256 -#define CROSS_LEN 512 - - -static char temp_keyword[1024]; +#define MAX_LINE_LENGTH 512 +#define MAX_FILENAME_LENGTH 256 +#define CROSS_LEN 512 +static char temp_keyword[1024]; #ifdef ENABLE_CDROM_IMAGE_BACKEND_LOG int cdrom_image_backend_do_log = ENABLE_CDROM_IMAGE_BACKEND_LOG; - void cdrom_image_backend_log(const char *fmt, ...) { @@ -65,10 +60,9 @@ cdrom_image_backend_log(const char *fmt, ...) } } #else -#define cdrom_image_backend_log(fmt, ...) +# define cdrom_image_backend_log(fmt, ...) #endif - /* Binary file functions. */ static int bin_read(void *p, uint8_t *buffer, uint64_t seek, size_t count) @@ -98,11 +92,10 @@ bin_read(void *p, uint8_t *buffer, uint64_t seek, size_t count) return 1; } - static uint64_t bin_get_length(void *p) { - off64_t len; + off64_t len; track_file_t *tf = (track_file_t *) p; cdrom_image_backend_log("CDROM: binary_length(%08lx)\n", tf->file); @@ -117,7 +110,6 @@ bin_get_length(void *p) return len; } - static void bin_close(void *p) { @@ -136,7 +128,6 @@ bin_close(void *p) free(p); } - static track_file_t * bin_init(const char *filename, int *error) { @@ -156,9 +147,9 @@ bin_init(const char *filename, int *error) /* Set the function pointers. */ if (!*error) { - tf->read = bin_read; + tf->read = bin_read; tf->get_length = bin_get_length; - tf->close = bin_close; + tf->close = bin_close; } else { free(tf); tf = NULL; @@ -167,7 +158,6 @@ bin_init(const char *filename, int *error) return tf; } - static track_file_t * track_file_init(const char *filename, int *error) { @@ -176,7 +166,6 @@ track_file_init(const char *filename, int *error) return bin_init(filename, error); } - static void track_file_close(track_t *trk) { @@ -193,14 +182,13 @@ track_file_close(track_t *trk) trk->file = NULL; } - /* Root functions. */ static void cdi_clear_tracks(cd_img_t *cdi) { - int i; + int i; track_file_t *last = NULL; - track_t *cur = NULL; + track_t *cur = NULL; if ((cdi->tracks == NULL) || (cdi->tracks_num == 0)) return; @@ -224,7 +212,6 @@ cdi_clear_tracks(cd_img_t *cdi) cdi->tracks_num = 0; } - void cdi_close(cd_img_t *cdi) { @@ -232,7 +219,6 @@ cdi_close(cd_img_t *cdi) free(cdi); } - int cdi_set_device(cd_img_t *cdi, const char *path) { @@ -240,36 +226,33 @@ cdi_set_device(cd_img_t *cdi, const char *path) return 1; if (cdi_load_iso(cdi, path)) - return 1; + return 2; return 0; } - /* TODO: This never returns anything other than 1, should it even be an int? */ int cdi_get_audio_tracks(cd_img_t *cdi, int *st_track, int *end, TMSF *lead_out) { *st_track = 1; - *end = cdi->tracks_num - 1; + *end = cdi->tracks_num - 1; FRAMES_TO_MSF(cdi->tracks[*end].start + 150, &lead_out->min, &lead_out->sec, &lead_out->fr); return 1; } - /* TODO: This never returns anything other than 1, should it even be an int? */ int cdi_get_audio_tracks_lba(cd_img_t *cdi, int *st_track, int *end, uint32_t *lead_out) { *st_track = 1; - *end = cdi->tracks_num - 1; + *end = cdi->tracks_num - 1; *lead_out = cdi->tracks[*end].start; return 1; } - int cdi_get_audio_track_pre(cd_img_t *cdi, int track) { @@ -281,13 +264,12 @@ cdi_get_audio_track_pre(cd_img_t *cdi, int track) return trk->pre; } - /* This replaces both Info and EndInfo, they are specified by a variable. */ int cdi_get_audio_track_info(cd_img_t *cdi, int end, int track, int *track_num, TMSF *start, uint8_t *attr) { track_t *trk = &cdi->tracks[track - 1]; - int pos = trk->start + 150; + int pos = trk->start + 150; if ((track < 1) || (track > cdi->tracks_num)) return 0; @@ -297,12 +279,11 @@ cdi_get_audio_track_info(cd_img_t *cdi, int end, int track, int *track_num, TMSF FRAMES_TO_MSF(pos, &start->min, &start->sec, &start->fr); *track_num = trk->track_number; - *attr = trk->attr; + *attr = trk->attr; return 1; } - int cdi_get_audio_track_info_lba(cd_img_t *cdi, int end, int track, int *track_num, uint32_t *start, uint8_t *attr) { @@ -314,16 +295,15 @@ cdi_get_audio_track_info_lba(cd_img_t *cdi, int end, int track, int *track_num, *start = (uint32_t) trk->start; *track_num = trk->track_number; - *attr = trk->attr; + *attr = trk->attr; return 1; } - int cdi_get_track(cd_img_t *cdi, uint32_t sector) { - int i; + int i; track_t *cur, *next; /* There must be at least two tracks - data and lead out. */ @@ -333,7 +313,7 @@ cdi_get_track(cd_img_t *cdi, uint32_t sector) /* This has a problem - the code skips the last track, which is lead out - is that correct? */ for (i = 0; i < (cdi->tracks_num - 1); i++) { - cur = &cdi->tracks[i]; + cur = &cdi->tracks[i]; next = &cdi->tracks[i + 1]; if ((cur->start <= sector) && (sector < next->start)) return cur->number; @@ -342,20 +322,19 @@ cdi_get_track(cd_img_t *cdi, uint32_t sector) return -1; } - /* TODO: See if track start is adjusted by 150 or not. */ int cdi_get_audio_sub(cd_img_t *cdi, uint32_t sector, uint8_t *attr, uint8_t *track, uint8_t *index, TMSF *rel_pos, TMSF *abs_pos) { - int cur_track = cdi_get_track(cdi, sector); + int cur_track = cdi_get_track(cdi, sector); track_t *trk; if (cur_track < 1) return 0; *track = (uint8_t) cur_track; - trk = &cdi->tracks[*track - 1]; - *attr = trk->attr; + trk = &cdi->tracks[*track - 1]; + *attr = trk->attr; *index = 1; FRAMES_TO_MSF(sector + 150, &abs_pos->min, &abs_pos->sec, &abs_pos->fr); @@ -366,23 +345,22 @@ cdi_get_audio_sub(cd_img_t *cdi, uint32_t sector, uint8_t *attr, uint8_t *track, return 1; } - int cdi_read_sector(cd_img_t *cdi, uint8_t *buffer, int raw, uint32_t sector) { - size_t length; - int track = cdi_get_track(cdi, sector) - 1; - uint64_t sect = (uint64_t) sector, seek; + size_t length; + int track = cdi_get_track(cdi, sector) - 1; + uint64_t sect = (uint64_t) sector, seek; track_t *trk; - int track_is_raw, ret; - int raw_size, cooked_size; + int track_is_raw, ret; + int raw_size, cooked_size; uint64_t offset = 0ULL; - int m = 0, s = 0, f = 0; + int m = 0, s = 0, f = 0; if (track < 0) return 0; - trk = &cdi->tracks[track]; + trk = &cdi->tracks[track]; track_is_raw = ((trk->sector_size == RAW_SECTOR_SIZE) || (trk->sector_size == 2448)); seek = trk->skip + ((sect - trk->start) * trk->sector_size); @@ -394,7 +372,7 @@ cdi_read_sector(cd_img_t *cdi, uint8_t *buffer, int raw, uint32_t sector) if (trk->mode2 && (trk->form != 1)) { if (trk->form == 2) - cooked_size = (track_is_raw ? 2328 : trk->sector_size); /* Both 2324 + ECC and 2328 variants are valid. */ + cooked_size = (track_is_raw ? 2328 : trk->sector_size); /* Both 2324 + ECC and 2328 variants are valid. */ else cooked_size = 2336; } else @@ -429,19 +407,18 @@ cdi_read_sector(cd_img_t *cdi, uint8_t *buffer, int raw, uint32_t sector) return trk->file->read(trk->file, buffer, seek, length); } - int cdi_read_sectors(cd_img_t *cdi, uint8_t *buffer, int raw, uint32_t sector, uint32_t num) { - int sector_size, success = 1; + int sector_size, success = 1; uint8_t *buf; uint32_t buf_len, i; /* TODO: This fails to account for Mode 2. Shouldn't we have a function to get sector size? */ sector_size = raw ? RAW_SECTOR_SIZE : COOKED_SECTOR_SIZE; - buf_len = num * sector_size; - buf = (uint8_t *) malloc(buf_len * sizeof(uint8_t)); + buf_len = num * sector_size; + buf = (uint8_t *) malloc(buf_len * sizeof(uint8_t)); for (i = 0; i < num; i++) { success = cdi_read_sector(cdi, &buf[i * sector_size], raw, sector + i); @@ -456,19 +433,18 @@ cdi_read_sectors(cd_img_t *cdi, uint8_t *buffer, int raw, uint32_t sector, uint3 return success; } - /* TODO: Do CUE+BIN images with a sector size of 2448 even exist? */ int cdi_read_sector_sub(cd_img_t *cdi, uint8_t *buffer, uint32_t sector) { - int track = cdi_get_track(cdi, sector) - 1; + int track = cdi_get_track(cdi, sector) - 1; track_t *trk; uint64_t s = (uint64_t) sector, seek; if (track < 0) return 0; - trk = &cdi->tracks[track]; + trk = &cdi->tracks[track]; seek = trk->skip + ((s - trk->start) * trk->sector_size); if (trk->sector_size != 2448) return 0; @@ -476,11 +452,10 @@ cdi_read_sector_sub(cd_img_t *cdi, uint8_t *buffer, uint32_t sector) return trk->file->read(trk->file, buffer, seek, 2448); } - int cdi_get_sector_size(cd_img_t *cdi, uint32_t sector) { - int track = cdi_get_track(cdi, sector) - 1; + int track = cdi_get_track(cdi, sector) - 1; track_t *trk; if (track < 0) @@ -490,11 +465,10 @@ cdi_get_sector_size(cd_img_t *cdi, uint32_t sector) return trk->sector_size; } - int cdi_is_mode2(cd_img_t *cdi, uint32_t sector) { - int track = cdi_get_track(cdi, sector) - 1; + int track = cdi_get_track(cdi, sector) - 1; track_t *trk; if (track < 0) @@ -505,11 +479,10 @@ cdi_is_mode2(cd_img_t *cdi, uint32_t sector) return !!(trk->mode2); } - int cdi_get_mode2_form(cd_img_t *cdi, uint32_t sector) { - int track = cdi_get_track(cdi, sector) - 1; + int track = cdi_get_track(cdi, sector) - 1; track_t *trk; if (track < 0) @@ -520,12 +493,11 @@ cdi_get_mode2_form(cd_img_t *cdi, uint32_t sector) return trk->form; } - static int cdi_can_read_pvd(track_file_t *file, uint64_t sector_size, int mode2, int form) { - uint8_t pvd[COOKED_SECTOR_SIZE]; - uint64_t seek = 16ULL * sector_size; /* First VD is located at sector 16. */ + uint8_t pvd[COOKED_SECTOR_SIZE]; + uint64_t seek = 16ULL * sector_size; /* First VD is located at sector 16. */ if ((!mode2 || (form == 0)) && (sector_size == RAW_SECTOR_SIZE)) seek += 16; @@ -534,11 +506,9 @@ cdi_can_read_pvd(track_file_t *file, uint64_t sector_size, int mode2, int form) file->read(file, pvd, seek, COOKED_SECTOR_SIZE); - return ((pvd[0] == 1 && !strncmp((char*)(&pvd[1]), "CD001", 5) && pvd[6] == 1) || - (pvd[8] == 1 && !strncmp((char*)(&pvd[9]), "CDROM", 5) && pvd[14] == 1)); + return ((pvd[0] == 1 && !strncmp((char *) (&pvd[1]), "CD001", 5) && pvd[6] == 1) || (pvd[8] == 1 && !strncmp((char *) (&pvd[9]), "CDROM", 5) && pvd[14] == 1)); } - /* This reallocates the array and returns the pointer to the last track. */ static void cdi_track_push_back(cd_img_t *cdi, track_t *trk) @@ -555,14 +525,13 @@ cdi_track_push_back(cd_img_t *cdi, track_t *trk) cdi->tracks_num++; } - int cdi_load_iso(cd_img_t *cdi, const char *filename) { - int error; + int error; track_t trk; - cdi->tracks = NULL; + cdi->tracks = NULL; cdi->tracks_num = 0; memset(&trk, 0, sizeof(track_t)); @@ -572,28 +541,33 @@ cdi_load_iso(cd_img_t *cdi, const char *filename) if (error) { if ((trk.file != NULL) && (trk.file->close != NULL)) trk.file->close(trk.file); - return 0; + trk.file = viso_init(filename, &error); + if (error) { + if ((trk.file != NULL) && (trk.file->close != NULL)) + trk.file->close(trk.file); + return 0; + } } - trk.number = 1; + trk.number = 1; trk.track_number = 1; - trk.attr = DATA_TRACK; + trk.attr = DATA_TRACK; /* Try to detect ISO type. */ - trk.form = 0; + trk.form = 0; trk.mode2 = 0; /* TODO: Merge the first and last cases since they result in the same thing. */ if (cdi_can_read_pvd(trk.file, RAW_SECTOR_SIZE, 0, 0)) trk.sector_size = RAW_SECTOR_SIZE; else if (cdi_can_read_pvd(trk.file, 2336, 1, 0)) { trk.sector_size = 2336; - trk.mode2 = 1; + trk.mode2 = 1; } else if (cdi_can_read_pvd(trk.file, 2324, 1, 2)) { trk.sector_size = 2324; - trk.mode2 = 1; - trk.form = 2; + trk.mode2 = 1; + trk.form = 2; } else if (cdi_can_read_pvd(trk.file, RAW_SECTOR_SIZE, 1, 0)) { trk.sector_size = RAW_SECTOR_SIZE; - trk.mode2 = 1; + trk.mode2 = 1; } else { /* We use 2048 mode 1 as the default. */ trk.sector_size = COOKED_SECTOR_SIZE; @@ -604,30 +578,29 @@ cdi_load_iso(cd_img_t *cdi, const char *filename) cdi_track_push_back(cdi, &trk); /* Lead out track. */ - trk.number = 2; + trk.number = 2; trk.track_number = 0xAA; - trk.attr = 0x16; /* Was originally 0x00, but I believe 0x16 is appropriate. */ - trk.start = trk.length; - trk.length = 0; - trk.file = NULL; + trk.attr = 0x16; /* Was originally 0x00, but I believe 0x16 is appropriate. */ + trk.start = trk.length; + trk.length = 0; + trk.file = NULL; cdi_track_push_back(cdi, &trk); return 1; } - static int cdi_cue_get_buffer(char *str, char **line, int up) { - char *s = *line; - char *p = str; - int quote = 0; - int done = 0; - int space = 1; + char *s = *line; + char *p = str; + int quote = 0; + int done = 0; + int space = 1; /* Copy to local buffer until we have end of string or whitespace. */ - while (! done) { - switch(*s) { + while (!done) { + switch (*s) { case '\0': if (quote) { /* Ouch, unterminated string.. */ @@ -640,11 +613,12 @@ cdi_cue_get_buffer(char *str, char **line, int up) quote ^= 1; break; - case ' ': case '\t': + case ' ': + case '\t': if (space) - break; + break; - if (! quote) { + if (!quote) { done = 1; break; } @@ -659,7 +633,7 @@ cdi_cue_get_buffer(char *str, char **line, int up) break; } - if (! done) + if (!done) s++; } *p = '\0'; @@ -669,7 +643,6 @@ cdi_cue_get_buffer(char *str, char **line, int up) return 1; } - static int cdi_cue_get_keyword(char **dest, char **line) { @@ -682,12 +655,11 @@ cdi_cue_get_keyword(char **dest, char **line) return success; } - /* Get a string from the input line, handling quotes properly. */ static uint64_t cdi_cue_get_number(char **line) { - char temp[128]; + char temp[128]; uint64_t num; if (!cdi_cue_get_buffer(temp, line, 0)) @@ -699,13 +671,12 @@ cdi_cue_get_number(char **line) return num; } - static int cdi_cue_get_frame(uint64_t *frames, char **line) { char temp[128]; - int min, sec, fr; - int success; + int min, sec, fr; + int success; success = cdi_cue_get_buffer(temp, line, 0); if (!success) @@ -720,12 +691,11 @@ cdi_cue_get_frame(uint64_t *frames, char **line) return 1; } - static int cdi_cue_get_flags(track_t *cur, char **line) { char temp[128], temp2[128]; - int success; + int success; success = cdi_cue_get_buffer(temp, line, 0); if (!success) @@ -741,7 +711,6 @@ cdi_cue_get_flags(track_t *cur, char **line) return 1; } - static int cdi_add_track(cd_img_t *cdi, track_t *cur, uint64_t *shift, uint64_t prestart, uint64_t *total_pregap, uint64_t cur_pregap) { @@ -784,11 +753,11 @@ cdi_add_track(cd_img_t *cdi, track_t *cur, uint64_t *shift, uint64_t prestart, u *total_pregap += cur_pregap; cur->start += *total_pregap; } else { - temp = prev->file->get_length(prev->file) - ((uint64_t) prev->skip); + temp = prev->file->get_length(prev->file) - ((uint64_t) prev->skip); prev->length = temp / ((uint64_t) prev->sector_size); if ((temp % prev->sector_size) != 0) prev->length++; - /* Padding. */ + /* Padding. */ cur->start += prev->start + prev->length + cur_pregap; cur->skip = skip * cur->sector_size; @@ -809,24 +778,23 @@ cdi_add_track(cd_img_t *cdi, track_t *cur, uint64_t *shift, uint64_t prestart, u return 1; } - int cdi_load_cue(cd_img_t *cdi, const char *cuefile) { - track_t trk; - char pathname[MAX_FILENAME_LENGTH], filename[MAX_FILENAME_LENGTH]; - char temp[MAX_FILENAME_LENGTH]; + track_t trk; + char pathname[MAX_FILENAME_LENGTH], filename[MAX_FILENAME_LENGTH]; + char temp[MAX_FILENAME_LENGTH]; uint64_t shift = 0ULL, prestart = 0ULL; uint64_t cur_pregap = 0ULL, total_pregap = 0ULL; uint64_t frame = 0ULL, index; - int i, success; - int error, can_add_track = 0; - FILE *fp; - char buf[MAX_LINE_LENGTH], ansi[MAX_FILENAME_LENGTH]; - char *line, *command; - char *type; + int i, success; + int error, can_add_track = 0; + FILE *fp; + char buf[MAX_LINE_LENGTH], ansi[MAX_FILENAME_LENGTH]; + char *line, *command; + char *type; - cdi->tracks = NULL; + cdi->tracks = NULL; cdi->tracks_num = 0; memset(&trk, 0, sizeof(track_t)); @@ -855,10 +823,10 @@ cdi_load_cue(cd_img_t *cdi, const char *cuefile) if (strlen(buf) > 0) { if (buf[strlen(buf) - 1] == '\n') buf[strlen(buf) - 1] = '\0'; - /* nuke trailing newline */ + /* nuke trailing newline */ else if (buf[strlen(buf) - 1] == '\r') buf[strlen(buf) - 1] = '\0'; - /* nuke trailing newline */ + /* nuke trailing newline */ } } @@ -872,86 +840,86 @@ cdi_load_cue(cd_img_t *cdi, const char *cuefile) if (!success) break; - trk.start = 0; - trk.skip = 0; + trk.start = 0; + trk.skip = 0; cur_pregap = 0; - prestart = 0; + prestart = 0; - trk.number = cdi_cue_get_number(&line); + trk.number = cdi_cue_get_number(&line); trk.track_number = trk.number; - success = cdi_cue_get_keyword(&type, &line); + success = cdi_cue_get_keyword(&type, &line); if (!success) break; - trk.form = 0; + trk.form = 0; trk.mode2 = 0; trk.pre = 0; if (!strcmp(type, "AUDIO")) { trk.sector_size = RAW_SECTOR_SIZE; - trk.attr = AUDIO_TRACK; + trk.attr = AUDIO_TRACK; } else if (!strcmp(type, "MODE1/2048")) { trk.sector_size = COOKED_SECTOR_SIZE; - trk.attr = DATA_TRACK; + trk.attr = DATA_TRACK; } else if (!strcmp(type, "MODE1/2352")) { trk.sector_size = RAW_SECTOR_SIZE; - trk.attr = DATA_TRACK; + trk.attr = DATA_TRACK; } else if (!strcmp(type, "MODE1/2448")) { trk.sector_size = 2448; - trk.attr = DATA_TRACK; + trk.attr = DATA_TRACK; } else if (!strcmp(type, "MODE2/2048")) { - trk.form = 1; + trk.form = 1; trk.sector_size = COOKED_SECTOR_SIZE; - trk.attr = DATA_TRACK; - trk.mode2 = 1; + trk.attr = DATA_TRACK; + trk.mode2 = 1; } else if (!strcmp(type, "MODE2/2324")) { - trk.form = 2; + trk.form = 2; trk.sector_size = 2324; - trk.attr = DATA_TRACK; - trk.mode2 = 1; + trk.attr = DATA_TRACK; + trk.mode2 = 1; } else if (!strcmp(type, "MODE2/2328")) { - trk.form = 2; + trk.form = 2; trk.sector_size = 2328; - trk.attr = DATA_TRACK; - trk.mode2 = 1; + trk.attr = DATA_TRACK; + trk.mode2 = 1; } else if (!strcmp(type, "MODE2/2336")) { trk.sector_size = 2336; - trk.attr = DATA_TRACK; - trk.mode2 = 1; + trk.attr = DATA_TRACK; + trk.mode2 = 1; } else if (!strcmp(type, "MODE2/2352")) { /* Assume this is XA Mode 2 Form 1. */ - trk.form = 1; + trk.form = 1; trk.sector_size = RAW_SECTOR_SIZE; - trk.attr = DATA_TRACK; - trk.mode2 = 1; + trk.attr = DATA_TRACK; + trk.mode2 = 1; } else if (!strcmp(type, "MODE2/2448")) { /* Assume this is XA Mode 2 Form 1. */ - trk.form = 1; + trk.form = 1; trk.sector_size = 2448; - trk.attr = DATA_TRACK; - trk.mode2 = 1; + trk.attr = DATA_TRACK; + trk.mode2 = 1; } else if (!strcmp(type, "CDG/2448")) { trk.sector_size = 2448; - trk.attr = DATA_TRACK; - trk.mode2 = 1; + trk.attr = DATA_TRACK; + trk.mode2 = 1; } else if (!strcmp(type, "CDI/2336")) { trk.sector_size = 2336; - trk.attr = DATA_TRACK; - trk.mode2 = 1; + trk.attr = DATA_TRACK; + trk.mode2 = 1; } else if (!strcmp(type, "CDI/2352")) { trk.sector_size = RAW_SECTOR_SIZE; - trk.attr = DATA_TRACK; - trk.mode2 = 1; + trk.attr = DATA_TRACK; + trk.mode2 = 1; } else success = 0; can_add_track = 1; } else if (!strcmp(command, "INDEX")) { - index = cdi_cue_get_number(&line); + index = cdi_cue_get_number(&line); success = cdi_cue_get_frame(&frame, &line); - switch(index) { + switch (index) { case 0: prestart = frame; break; @@ -984,7 +952,7 @@ cdi_load_cue(cd_img_t *cdi, const char *cuefile) break; trk.file = NULL; - error = 1; + error = 1; if (!strcmp(type, "BINARY")) { memset(temp, 0, MAX_FILENAME_LENGTH * sizeof(char)); @@ -1006,9 +974,7 @@ cdi_load_cue(cd_img_t *cdi, const char *cuefile) success = cdi_cue_get_frame(&cur_pregap, &line); else if (!strcmp(command, "FLAGS")) success = cdi_cue_get_flags(&trk, &line); - else if (!strcmp(command, "CATALOG") || !strcmp(command, "CDTEXTFILE") || !strcmp(command, "ISRC") || - !strcmp(command, "PERFORMER") || !strcmp(command, "POSTGAP") || !strcmp(command, "REM") || - !strcmp(command, "SONGWRITER") || !strcmp(command, "TITLE") || !strcmp(command, "")) { + else if (!strcmp(command, "CATALOG") || !strcmp(command, "CDTEXTFILE") || !strcmp(command, "ISRC") || !strcmp(command, "PERFORMER") || !strcmp(command, "POSTGAP") || !strcmp(command, "REM") || !strcmp(command, "SONGWRITER") || !strcmp(command, "TITLE") || !strcmp(command, "")) { /* Ignored commands. */ success = 1; } else { @@ -1016,7 +982,7 @@ cdi_load_cue(cd_img_t *cdi, const char *cuefile) cdrom_image_backend_log("CUE: unsupported command '%s' in cue sheet!\n", command); #endif success = 0; - } + } if (!success) break; @@ -1033,17 +999,16 @@ cdi_load_cue(cd_img_t *cdi, const char *cuefile) /* Add lead out track. */ trk.number++; trk.track_number = 0xAA; - trk.attr = 0x16; /* Was 0x00 but I believe 0x16 is appropriate. */ - trk.start = 0; - trk.length = 0; - trk.file = NULL; + trk.attr = 0x16; /* Was 0x00 but I believe 0x16 is appropriate. */ + trk.start = 0; + trk.length = 0; + trk.file = NULL; if (!cdi_add_track(cdi, &trk, &shift, 0, &total_pregap, 0)) return 0; return 1; } - int cdi_has_data_track(cd_img_t *cdi) { @@ -1061,7 +1026,6 @@ cdi_has_data_track(cd_img_t *cdi) return 0; } - int cdi_has_audio_track(cd_img_t *cdi) { diff --git a/src/cdrom/cdrom_image_viso.c b/src/cdrom/cdrom_image_viso.c new file mode 100644 index 000000000..25e3466f5 --- /dev/null +++ b/src/cdrom/cdrom_image_viso.c @@ -0,0 +1,1112 @@ +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * Virtual ISO CD-ROM image back-end. + * + * Authors: RichardG + * + * Copyright 2022 RichardG. + */ +// clang-format off +#define _LARGEFILE_SOURCE +#define _LARGEFILE64_SOURCE +#define __STDC_FORMAT_MACROS +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define HAVE_STDARG_H +#include <86box/86box.h> +#include <86box/bswap.h> +#include <86box/cdrom_image_backend.h> +#include <86box/plat.h> +#include <86box/plat_dir.h> +#include <86box/version.h> +#include <86box/timer.h> +#include <86box/nvr.h> +// clang-format on + +#define VISO_SKIP(p, n) \ + memset(p, 0x00, n); \ + p += n; + +/* ISO 9660 defines "both endian" data formats, which are + stored as little endian followed by big endian. */ +#define VISO_LBE_16(p, x) \ + *((uint16_t *) p) = cpu_to_le16(x); \ + p += 2; \ + *((uint16_t *) p) = cpu_to_be16(x); \ + p += 2; +#define VISO_LBE_32(p, x) \ + *((uint32_t *) p) = cpu_to_le32(x); \ + p += 4; \ + *((uint32_t *) p) = cpu_to_be32(x); \ + p += 4; + +#define VISO_SECTOR_SIZE COOKED_SECTOR_SIZE +#define VISO_OPEN_FILES 32 + +enum { + VISO_CHARSET_D = 0, + VISO_CHARSET_A, + VISO_CHARSET_FN, + VISO_CHARSET_ANY +}; + +enum { + VISO_DIR_CURRENT = 0, + VISO_DIR_PARENT = 1, + VISO_DIR_REGULAR, + VISO_DIR_JOLIET +}; + +typedef struct _viso_entry_ { + char *path, name_short[13], name_rr[256]; + union { /* save some memory */ + uint64_t pt_offsets[4]; + FILE *file; + }; + uint64_t dr_offsets[2], data_offset; + uint16_t name_joliet[111], pt_idx; + + struct stat stats; + + struct _viso_entry_ *parent, *next, *next_dir, *first_child; +} viso_entry_t; + +typedef struct { + uint64_t vol_size_offsets[2], pt_meta_offsets[2]; + uint32_t metadata_sectors, all_sectors, entry_map_size; + unsigned int sector_size, file_fifo_pos; + uint8_t *metadata; + + track_file_t tf; + viso_entry_t root_dir, **entry_map, *file_fifo[VISO_OPEN_FILES]; +} viso_t; + +#define ENABLE_CDROM_IMAGE_VISO_LOG 1 +#ifdef ENABLE_CDROM_IMAGE_VISO_LOG +int cdrom_image_viso_do_log = ENABLE_CDROM_IMAGE_VISO_LOG; + +void +cdrom_image_viso_log(const char *fmt, ...) +{ + va_list ap; + + if (cdrom_image_viso_do_log) { + va_start(ap, fmt); + pclog_ex(fmt, ap); + va_end(ap); + } +} +#else +# define cdrom_image_viso_log(fmt, ...) +#endif + +static size_t +viso_pread(void *ptr, uint64_t offset, size_t size, size_t count, FILE *stream) +{ + uint64_t cur_pos = ftello64(stream); + size_t ret = 0; + if (fseeko64(stream, offset, SEEK_SET) != -1) + ret = fread(ptr, size, count, stream); + fseeko64(stream, cur_pos, SEEK_SET); + return ret; +} + +static size_t +viso_pwrite(const void *ptr, uint64_t offset, size_t size, size_t count, FILE *stream) +{ + uint64_t cur_pos = ftello64(stream); + size_t ret = 0; + if (fseeko64(stream, offset, SEEK_SET) != -1) + ret = fwrite(ptr, size, count, stream); + fseeko64(stream, cur_pos, SEEK_SET); + return ret; +} + +#define VISO_WRITE_STR_FUNC(n, t, st, cnv) \ + static void \ + n(t *dest, const st *src, int buf_size, int charset) \ + { \ + while (*src && (buf_size-- > 0)) { \ + switch (*src) { \ + case 'A' ... 'Z': \ + case '0' ... '9': \ + case '_': \ + /* Valid on all sets. */ \ + *dest = *src; \ + break; \ + \ + case 'a' ... 'z': \ + /* Convert to uppercase on A and D. */ \ + if (charset >= VISO_CHARSET_FN) \ + *dest = *src; \ + else \ + *dest = *src - 32; \ + break; \ + \ + case ' ': \ + case '!': \ + case '"': \ + case '%': \ + case '&': \ + case '(': \ + case ')': \ + case '+': \ + case ',': \ + case '-': \ + case '.': \ + case '<': \ + case '=': \ + case '>': \ + /* Valid for A and filenames but not for D. */ \ + if (charset >= VISO_CHARSET_A) \ + *dest = *src; \ + else \ + *dest = '_'; \ + break; \ + \ + case '*': \ + case '/': \ + case ':': \ + case ';': \ + case '?': \ + case '\'': \ + /* Valid for A but not for filenames or D. */ \ + if ((charset >= VISO_CHARSET_A) && (charset != VISO_CHARSET_FN)) \ + *dest = *src; \ + else \ + *dest = '_'; \ + break; \ + \ + case 0x00 ... 0x1f: \ + /* Not valid for A, D or filenames. */ \ + if (charset > VISO_CHARSET_FN) \ + *dest = *src; \ + else \ + *dest = '_'; \ + \ + default: \ + /* Not valid for A or D, but valid for filenames. */ \ + if ((charset >= VISO_CHARSET_FN) && (*src <= 0xffff)) \ + *dest = *src; \ + else \ + *dest = '_'; \ + } \ + \ + *dest = cnv(*dest); \ + \ + dest++; \ + src++; \ + } \ + \ + /* Apply space padding. */ \ + while (buf_size-- > 0) \ + *dest++ = cnv(' '); \ + } +VISO_WRITE_STR_FUNC(viso_write_string, uint8_t, char, ) +VISO_WRITE_STR_FUNC(viso_write_wstring, uint16_t, wchar_t, cpu_to_be16) + +static int +viso_get_short_filename(viso_entry_t *dir, char *dest, const char *src) +{ + /* Get name and extension length. */ + const char *ext_pos = strrchr(src, '.'); + int name_len, ext_len; + if (ext_pos) { + name_len = ext_pos - src; + ext_len = strlen(ext_pos); + } else { + name_len = strlen(src); + ext_len = 0; + } + + /* Copy name. */ + int name_copy_len = MIN(8, name_len); + viso_write_string((uint8_t *) dest, src, name_copy_len, VISO_CHARSET_D); + dest[name_copy_len] = 0; + + /* Copy extension to temporary buffer. */ + char ext[5] = { 0 }; + int force_tail = (name_len > 8) || (ext_len == 1); + if (ext_len > 1) { + ext[0] = '.'; + if (ext_len > 4) + force_tail = 1; + viso_write_string((uint8_t *) &ext[1], &ext_pos[1], MIN(ext_len, 4) - 1, VISO_CHARSET_D); + } + + /* Check if this filename is unique, and add a tail if required, while also adding the extension. */ + char tail[8]; + for (int i = force_tail; i <= 999999; i++) { + /* Add tail to the filename if this is not the first run. */ + int tail_len = -1; + if (i) { + tail_len = sprintf(tail, "~%d", i); + strcpy(&dest[MIN(name_copy_len, 8 - tail_len)], tail); + } + + /* Add extension to the filename if present. */ + if (ext[0]) + strcat(dest, ext); + + /* Go through files in this directory to make sure this filename is unique. */ + viso_entry_t *entry = dir->first_child; + while (entry) { + /* Flag and stop if this filename was seen. */ + if ((entry->name_short != dest) && !strcmp(dest, entry->name_short)) { + tail_len = 0; + break; + } + + /* Move on to the next entry, and stop if the end of this directory was reached. */ + entry = entry->next; + if (entry && (entry->parent != dir)) + break; + } + + /* Stop if this is an unique name. */ + if (tail_len) + return 0; + } + return 1; +} + +static void +viso_fill_dir_record(viso_entry_t *entry, uint8_t *data, int type) +{ + uint8_t *p = data, *q; + + *p++ = 0; /* size (filled in later) */ + *p++ = 0; /* extended attribute length */ + VISO_SKIP(p, 8); /* sector offset */ + VISO_LBE_32(p, entry->stats.st_size); /* size (filled in later if this is a directory) */ + + time_t secs = entry->stats.st_mtime; + struct tm *time_s = gmtime(&secs); /* time, use UTC as timezones are not portable */ + *p++ = time_s->tm_year; /* year since 1900 */ + *p++ = 1 + time_s->tm_mon; /* month */ + *p++ = time_s->tm_mday; /* day */ + *p++ = time_s->tm_hour; /* hour */ + *p++ = time_s->tm_min; /* minute */ + *p++ = time_s->tm_sec; /* second */ + *p++ = 0; /* timezone */ + + *p++ = S_ISDIR(entry->stats.st_mode) ? 0x02 : 0x00; /* flags */ + + VISO_SKIP(p, 2); /* interleave unit/gap size */ + VISO_LBE_16(p, 1); /* volume sequence number */ + + switch (type) { + case VISO_DIR_CURRENT: + case VISO_DIR_PARENT: + *p++ = 1; /* file ID length */ + *p++ = (type == VISO_DIR_CURRENT) ? 0 : 1; /* magic value corresponding to . or .. */ + break; + + case VISO_DIR_REGULAR: + q = p++; /* save location of the file ID length for later */ + + *q = strlen(entry->name_short); + memcpy(p, entry->name_short, *q); /* file ID */ + p += *q; + if (!S_ISDIR(entry->stats.st_mode)) { + memcpy(p, ";1", 2); /* version suffix for files */ + p += 2; + *q += 2; + } + + if (!((*q) & 1)) /* padding for even file ID lengths */ + *p++ = 0; + + *p++ = 'R'; /* RR = present Rock Ridge entries (only documented by RRIP revision 1.09!) */ + *p++ = 'R'; + *p++ = 5; /* length */ + *p++ = 1; /* version */ + + q = p++; /* save location of Rock Ridge flags for later */ + + if (strcmp(entry->name_short, entry->name_rr)) { + *q |= 0x08; /* NM = alternate name */ + *p++ = 'N'; + *p++ = 'M'; + *p++ = 5 + MIN(128, strlen(entry->name_rr)); /* length */ + *p++ = 1; /* version */ + + *p++ = 0; /* flags */ + memcpy(p, entry->name_rr, *(p - 3) - 5); /* name */ + p += *(p - 3) - 5; + } + + *q |= 0x01; /* PX = POSIX attributes */ + *p++ = 'P'; + *p++ = 'X'; + *p++ = 36; /* length */ + *p++ = 1; /* version */ + + VISO_LBE_32(p, entry->stats.st_mode); /* mode */ + VISO_LBE_32(p, entry->stats.st_nlink); /* number of links */ + VISO_LBE_32(p, entry->stats.st_uid); /* owner UID */ + VISO_LBE_32(p, entry->stats.st_gid); /* owner GID */ + +#if defined(S_ISCHR) || defined(S_ISBLK) +# if defined(S_ISCHR) && defined(S_ISBLK) + if (S_ISCHR(entry->stats.st_mode) || S_ISBLK(entry->stats.st_mode)) +# elif defined(S_ISCHR) + if (S_ISCHR(entry->stats.st_mode)) +# else + if (S_ISBLK(entry->stats.st_mode)) +# endif + { + *q |= 0x02; /* PN = POSIX device */ + *p++ = 'P'; + *p++ = 'N'; + *p++ = 20; /* length */ + *p++ = 1; /* version */ + + VISO_LBE_32(p, 0); /* device high 32 bits */ + VISO_LBE_32(p, entry->stats.st_rdev); /* device low 32 bits */ + } +#endif +#ifdef S_ISLNK + if (S_ISLNK(entry->stats.st_mode)) { /* TODO: rather complex path splitting system */ + *q |= 0x04; /* SL = symlink */ + *p++ = 'S'; + *p++ = 'L'; + *p++ = 5; /* length */ + *p++ = 1; /* version */ + + *p++ = 0; /* flags */ + } +#endif + if (entry->stats.st_atime || entry->stats.st_mtime || entry->stats.st_ctime) { + *q |= 0x80; /* TF = timestamps */ + *p++ = 'T'; + *p++ = 'F'; + *p++ = 29; /* length */ + *p++ = 1; /* version */ + + *p++ = 0x0e; /* flags: modified | access | attributes */ + VISO_LBE_32(p, entry->stats.st_mtime); /* modified */ + VISO_LBE_32(p, entry->stats.st_atime); /* access */ + VISO_LBE_32(p, entry->stats.st_ctime); /* attributes */ + } + + if ((p - data) & 1) /* padding for odd Rock Ridge section lengths */ + *p++ = 0; + break; + + case VISO_DIR_JOLIET: + q = p++; /* save location of the file ID length for later */ + + uint16_t *s = entry->name_joliet; + *q = 0; + while (*s) { + *((uint16_t *) p) = *s++; /* file ID */ + p += 2; + *q += 2; + } + + if (!((*q) & 1)) /* padding for even file ID lengths */ + *p++ = 0; + break; + } + + data[0] = p - data; /* length */ +} + +int +viso_read(void *p, uint8_t *buffer, uint64_t seek, size_t count) +{ + track_file_t *tf = (track_file_t *) p; + viso_t *viso = (viso_t *) tf->priv; + + /* Handle reads in a sector by sector basis. */ + while (count > 0) { + /* Determine the current sector, offset and remainder. */ + uint32_t sector = seek / viso->sector_size, + sector_offset = seek % viso->sector_size, + sector_remain = MIN(count, viso->sector_size - sector_offset); + + /* Handle sector. */ + if (sector < viso->metadata_sectors) { + /* Copy metadata. */ + memcpy(buffer, viso->metadata + seek, sector_remain); + } else { + size_t read = 0; + + /* Get the file entry corresponding to this sector. */ + viso_entry_t *entry = viso->entry_map[sector - viso->metadata_sectors]; + if (entry) { + /* Open file if it's not already open. */ + if (!entry->file) { + /* Close any existing FIFO entry's file. */ + viso_entry_t *other_entry = viso->file_fifo[viso->file_fifo_pos]; + if (other_entry && other_entry->file) { + cdrom_image_viso_log("VISO: Displacing [%s]\n", other_entry->path); + fclose(other_entry->file); + other_entry->file = NULL; + } + + /* Open file. */ + cdrom_image_viso_log("VISO: Opening [%s]", entry->path); + if ((entry->file = fopen(entry->path, "rb"))) { + cdrom_image_viso_log("\n"); + + /* Add this entry to the FIFO. */ + viso->file_fifo[viso->file_fifo_pos++] = entry; + viso->file_fifo_pos &= (sizeof(viso->file_fifo) / sizeof(viso->file_fifo[0])) - 1; + } else { + cdrom_image_viso_log(" => failed\n"); + + /* Clear any existing FIFO entry. */ + viso->file_fifo[viso->file_fifo_pos] = NULL; + } + } + + /* Read data. */ + if (entry->file && (fseeko64(entry->file, seek - entry->data_offset, SEEK_SET) != -1)) + read = fread(buffer, 1, sector_remain, entry->file); + } + + /* Fill remainder with 00 bytes if needed. */ + if (read < sector_remain) + memset(buffer + read, 0x00, sector_remain - read); + } + + /* Move on to the next sector. */ + buffer += sector_remain; + seek += sector_remain; + count -= sector_remain; + } + + return 1; +} + +uint64_t +viso_get_length(void *p) +{ + track_file_t *tf = (track_file_t *) p; + viso_t *viso = (viso_t *) tf->priv; + return ((uint64_t) viso->all_sectors) * viso->sector_size; +} + +void +viso_close(void *p) +{ + track_file_t *tf = (track_file_t *) p; + viso_t *viso = (viso_t *) tf->priv; + + if (viso == NULL) + return; + + cdrom_image_viso_log("VISO: close()\n"); + + /* De-allocate everything. */ + if (tf->file) + fclose(tf->file); + + viso_entry_t *entry = &viso->root_dir, *next_entry; + while (entry) { + if (entry->path) + free(entry->path); + if (entry->file) + fclose(entry->file); + next_entry = entry->next; + if (entry != &viso->root_dir) + free(entry); + entry = next_entry; + } + + if (viso->metadata) + free(viso->metadata); + if (viso->entry_map) + free(viso->entry_map); + + free(viso); +} + +track_file_t * +viso_init(const char *dirname, int *error) +{ + cdrom_image_viso_log("VISO: init()\n"); + + /* Initialize our data structure. */ + viso_t *viso = (viso_t *) calloc(1, sizeof(viso_t)); + uint8_t *data = NULL, *p; + wchar_t *wtemp = NULL; + *error = 1; + if (viso == NULL) + goto end; + viso->sector_size = VISO_SECTOR_SIZE; + + /* Prepare temporary data buffers. */ + data = calloc(2, viso->sector_size); + int wtemp_len = MIN(64, sizeof(viso->root_dir.name_joliet) / sizeof(viso->root_dir.name_joliet[0])) + 1; + wtemp = malloc(wtemp_len * sizeof(wchar_t)); + if (!data || !wtemp) + goto end; + + /* Open temporary file. */ +#ifdef ENABLE_CDROM_IMAGE_VISO_LOG + strcpy(viso->tf.fn, "viso-debug.iso"); +#else + plat_tempfile(viso->tf.fn, "viso", ".tmp"); +#endif + viso->tf.file = plat_fopen64(nvr_path(viso->tf.fn), "w+b"); + if (!viso->tf.file) + goto end; + + /* Set up directory traversal. */ + cdrom_image_viso_log("VISO: Traversing directories:\n"); + viso_entry_t *dir = &viso->root_dir, *last_dir = dir, *last_entry = dir; + struct dirent *readdir_entry; + int max_len, len, name_len; + char *path; + + /* Fill root directory entry. */ + dir->path = (char *) malloc(strlen(dirname) + 1); + if (!dir->path) + goto end; + strcpy(dir->path, dirname); + stat(dirname, &dir->stats); + if (!S_ISDIR(dir->stats.st_mode)) + goto end; + dir->parent = dir; /* for path table filling */ + cdrom_image_viso_log("[%08X] %s => [root]\n", dir, dir->path); + + /* Traverse directories, starting with the root. */ + while (dir) { + /* Open directory for listing. */ + DIR *dirp = opendir(dir->path); + if (!dirp) + goto next_dir; + + /* Add . and .. pseudo-directories. */ + for (int i = 0; i < 2; i++) { + last_entry->next = (viso_entry_t *) calloc(1, sizeof(viso_entry_t)); + if (!last_entry->next) + goto end; + last_entry = last_entry->next; + last_entry->parent = dir; + if (!i) + dir->first_child = last_entry; + + /* Stat the current directory or parent directory. */ + stat(i ? dir->parent->path : dir->path, &last_entry->stats); + + /* Set short and long filenames. */ + strcpy(last_entry->name_short, i ? ".." : "."); + strcpy(last_entry->name_rr, i ? ".." : "."); + wcscpy(last_entry->name_joliet, i ? L".." : L"."); + + cdrom_image_viso_log("[%08X] %s => %s\n", last_entry, dir->path, last_entry->name_short); + } + + /* Iterate through this directory's children. */ + size_t dir_path_len = strlen(dir->path); + while ((readdir_entry = readdir(dirp))) { + /* Ignore . and .. pseudo-directories. */ + if (readdir_entry->d_name[0] == '.' && (readdir_entry->d_name[1] == '\0' || (readdir_entry->d_name[1] == '.' && readdir_entry->d_name[2] == '\0'))) + continue; + + /* Save full file path. */ + name_len = strlen(readdir_entry->d_name); + path = (char *) malloc(dir_path_len + name_len + 2); + if (!path) + goto end; + strcpy(path, dir->path); + plat_path_slash(path); + strcat(path, readdir_entry->d_name); + + /* Add and fill entry. */ + last_entry->next = (viso_entry_t *) calloc(1, sizeof(viso_entry_t)); + if (!last_entry->next) { + free(path); + goto end; + } + last_entry = last_entry->next; + last_entry->path = path; + last_entry->parent = dir; + + /* Stat this child. */ + if (stat(path, &last_entry->stats) != 0) { + /* Use a blank structure if stat failed. */ + memset(&last_entry->stats, 0x00, sizeof(struct stat)); + } + + /* Handle file size. */ + if (!S_ISDIR(last_entry->stats.st_mode)) { + /* Limit to 4 GB - 1 byte. */ + if (last_entry->stats.st_size > ((uint32_t) -1)) + last_entry->stats.st_size = (uint32_t) -1; + + /* Increase entry map size. */ + viso->entry_map_size += last_entry->stats.st_size / viso->sector_size; + if (last_entry->stats.st_size % viso->sector_size) + viso->entry_map_size++; /* round up to the next sector */ + } + + /* Set short filename. */ + if (viso_get_short_filename(dir, last_entry->name_short, readdir_entry->d_name)) + goto end; + + /* Set Rock Ridge long filename. */ + len = MIN(name_len, sizeof(last_entry->name_rr) - 1); + viso_write_string((uint8_t *) last_entry->name_rr, readdir_entry->d_name, len, VISO_CHARSET_FN); + last_entry->name_rr[len] = '\0'; + + /* Set Joliet long filename. */ + if (wtemp_len < (name_len + 1)) { /* grow wchar buffer if needed */ + wtemp_len = name_len + 1; + wtemp = realloc(wtemp, wtemp_len * sizeof(wchar_t)); + } + max_len = (sizeof(last_entry->name_joliet) / sizeof(last_entry->name_joliet[0])) - 1; + len = mbstowcs(wtemp, readdir_entry->d_name, wtemp_len - 1); + if (len > max_len) { + /* Relocate extension if this is a file whose name exceeds the maximum length. */ + if (!S_ISDIR(last_entry->stats.st_mode)) { + wchar_t *wext = wcsrchr(wtemp, L'.'); + if (wext) { + len = wcslen(wext); + memmove(wtemp + (max_len - len), wext, len * sizeof(wchar_t)); + } + } + len = max_len; + } + viso_write_wstring(last_entry->name_joliet, wtemp, len, VISO_CHARSET_FN); + last_entry->name_joliet[len] = '\0'; + + cdrom_image_viso_log("[%08X] %s => [%-12s] %s\n", last_entry, dir->path, last_entry->name_short, last_entry->name_rr); + + /* If this is a directory, add it to the traversal list. */ + if (S_ISDIR(last_entry->stats.st_mode)) { + last_dir->next_dir = last_entry; + last_dir = last_entry; + last_dir->first_child = NULL; + } + } + +next_dir: + /* Move on to the next directory. */ + dir = dir->next_dir; + } + + /* Write 16 blank sectors. */ + for (int i = 0; i < 16; i++) + fwrite(data, viso->sector_size, 1, viso->tf.file); + + /* Get current time for the volume descriptors. */ + time_t secs = time(NULL); + struct tm *time_s = gmtime(&secs); + + /* Get root directory basename for the volume ID. */ + char *basename = plat_get_basename(dirname); + if (!basename || (basename[0] == '\0')) + basename = EMU_NAME; + + /* Write volume descriptors. */ + for (int i = 0; i < 2; i++) { + /* Fill volume descriptor. */ + p = data; + *p++ = 1 + i; /* type */ + memcpy(p, "CD001", 5); /* standard ID */ + p += 5; + *p++ = 1; /* version */ + *p++ = 0; /* unused */ + + if (i) { + viso_write_wstring((uint16_t *) p, EMU_NAME_W, 16, VISO_CHARSET_A); /* system ID */ + p += 32; + mbstowcs(wtemp, basename, 16); + viso_write_wstring((uint16_t *) p, wtemp, 16, VISO_CHARSET_D); /* volume ID */ + p += 32; + } else { + viso_write_string(p, EMU_NAME, 32, VISO_CHARSET_A); /* system ID */ + p += 32; + viso_write_string(p, basename, 32, VISO_CHARSET_D); /* volume ID */ + p += 32; + } + + VISO_SKIP(p, 8); /* unused */ + + viso->vol_size_offsets[i] = ftello64(viso->tf.file) + (p - data); + VISO_LBE_32(p, 0); /* volume space size (filled in later) */ + + if (i) { + *p++ = 0x25; /* escape sequence (indicates our Joliet names are UCS-2 Level 3) */ + *p++ = 0x2f; + *p++ = 0x45; + VISO_SKIP(p, 32 - 3); /* unused */ + } else { + VISO_SKIP(p, 32); /* unused */ + } + + VISO_LBE_16(p, 1); /* volume set size */ + VISO_LBE_16(p, 1); /* volume sequence number */ + VISO_LBE_16(p, viso->sector_size); /* logical block size */ + + /* Path table metadata is filled in later. */ + viso->pt_meta_offsets[i] = ftello64(viso->tf.file) + (p - data); + VISO_LBE_32(p, 0); /* path table size */ + VISO_LBE_32(p, 0); /* little endian path table and optional path table sector (VISO_LBE_32 is a shortcut to set both) */ + VISO_LBE_32(p, 0); /* big endian path table and optional path table sector (VISO_LBE_32 is a shortcut to set both) */ + + viso->root_dir.dr_offsets[i] = ftello64(viso->tf.file) + (p - data); + viso_fill_dir_record(&viso->root_dir, p, VISO_DIR_CURRENT); /* root directory */ + p += p[0]; + + if (i) { + viso_write_wstring((uint16_t *) p, L"", 64, VISO_CHARSET_D); /* volume set ID */ + p += 128; + viso_write_wstring((uint16_t *) p, L"", 64, VISO_CHARSET_A); /* publisher ID */ + p += 128; + viso_write_wstring((uint16_t *) p, L"", 64, VISO_CHARSET_A); /* data preparer ID */ + p += 128; + swprintf(wtemp, 64, L"%ls %ls VIRTUAL ISO", EMU_NAME_W, EMU_VERSION_W); + viso_write_wstring((uint16_t *) p, wtemp, 64, VISO_CHARSET_A); /* application ID */ + p += 128; + viso_write_wstring((uint16_t *) p, L"", 18, VISO_CHARSET_D); /* copyright file ID */ + p += 37; + viso_write_wstring((uint16_t *) p, L"", 18, VISO_CHARSET_D); /* abstract file ID */ + p += 37; + viso_write_wstring((uint16_t *) p, L"", 18, VISO_CHARSET_D); /* bibliography file ID */ + p += 37; + } else { + viso_write_string(p, "", 128, VISO_CHARSET_D); /* volume set ID */ + p += 128; + viso_write_string(p, "", 128, VISO_CHARSET_A); /* publisher ID */ + p += 128; + viso_write_string(p, "", 128, VISO_CHARSET_A); /* data preparer ID */ + p += 128; + snprintf((char *) p, 128, "%s %s VIRTUAL ISO", EMU_NAME, EMU_VERSION); + viso_write_string(p, (char *) p, 128, VISO_CHARSET_A); /* application ID */ + p += 128; + viso_write_string(p, "", 37, VISO_CHARSET_D); /* copyright file ID */ + p += 37; + viso_write_string(p, "", 37, VISO_CHARSET_D); /* abstract file ID */ + p += 37; + viso_write_string(p, "", 37, VISO_CHARSET_D); /* bibliography file ID */ + p += 37; + } + + /* For the created/modified time, the string's NUL + terminator will act as our timezone offset of 0. */ + sprintf((char *) p, "%04d%02d%02d%02d%02d%02d%02d", /* volume created */ + 1900 + time_s->tm_year, 1 + time_s->tm_mon, time_s->tm_mday, + time_s->tm_hour, time_s->tm_min, time_s->tm_sec, 0); + strcpy((char *) (p + 17), (char *) p); /* volume modified */ + p += 34; + VISO_SKIP(p, 34); /* volume expires/effective */ + + *p++ = 1; /* file structure version */ + *p++ = 0; /* unused */ + + /* Blank the rest of the working sector. */ + memset(p, 0x00, viso->sector_size - (p - data)); + + /* Write volume descriptor. */ + fwrite(data, viso->sector_size, 1, viso->tf.file); + } + + /* Fill terminator. */ + p = data; + *p++ = 0xff; + memcpy(p, "CD001", 5); + p += 5; + *p++ = 0x01; + + /* Blank the rest of the working sector. */ + memset(p, 0x00, viso->sector_size - (p - data)); + + /* Write terminator. */ + fwrite(data, viso->sector_size, 1, viso->tf.file); + + /* We start seeing a pattern of padding to even sectors here. + mkisofs does this, presumably for a very good reason... */ + int write = ftello64(viso->tf.file) % (viso->sector_size * 2); + if (write) { + write = (viso->sector_size * 2) - write; + memset(data, 0x00, write); + fwrite(data, write, 1, viso->tf.file); + } + + /* Write each path table. */ + for (int i = 0; i < 4; i++) { + cdrom_image_viso_log("VISO: Generating path table #%d:\n", i); + + /* Save this path table's start offset. */ + uint64_t pt_start = ftello64(viso->tf.file); + + /* Write this table's sector offset to the corresponding volume descriptor. */ + uint32_t pt_temp = pt_start / viso->sector_size; + if (i & 1) + *((uint32_t *) data) = cpu_to_be32(pt_temp); + else + *((uint32_t *) data) = cpu_to_le32(pt_temp); + viso_pwrite(data, viso->pt_meta_offsets[i >> 1] + 8 + (8 * (i & 1)), 4, 1, viso->tf.file); + + /* Go through directories. */ + dir = &viso->root_dir; + uint16_t pt_idx = 1; + while (dir) { + /* Ignore . and .. pseudo-directories. */ + if (dir->name_short[0] == '.' && (dir->name_short[1] == '\0' || (dir->name_short[1] == '.' && dir->name_short[2] == '\0'))) { + dir = dir->next_dir; + continue; + } + + cdrom_image_viso_log("[%08X] %s => %s\n", dir, dir->path, (i & 2) ? dir->name_rr : dir->name_short); + + /* Save this directory's path table index and offset. */ + dir->pt_idx = pt_idx; + dir->pt_offsets[i] = ftello64(viso->tf.file); + + /* Fill path table entry. */ + if (dir == &viso->root_dir) /* directory ID length */ + data[0] = 1; + else if (i & 2) + data[0] = MIN(254, wcslen(dir->name_joliet) << 1); + else + data[0] = strlen(dir->name_short); + + data[1] = 0; /* extended attribute length */ + *((uint32_t *) &data[2]) = 0; /* extent location (filled in later) */ + if (i & 1) /* parent directory number */ + *((uint16_t *) &data[6]) = cpu_to_be16(dir->parent->pt_idx); + else + *((uint16_t *) &data[6]) = cpu_to_le16(dir->parent->pt_idx); + + if (i & 2) /* directory ID */ + memcpy(&data[8], dir->name_joliet, data[0]); + else + memcpy(&data[8], dir->name_short, data[0]); + data[data[0] + 8] = 0; /* padding for odd directory ID lengths */ + + /* Write path table entry. */ + fwrite(data, 8 + data[0] + (data[0] & 1), 1, viso->tf.file); + + /* Increment path table index and stop if it overflows. */ + if (++pt_idx == 0) + break; + + /* Move on to the next directory. */ + dir = dir->next_dir; + } + + /* Write this table's size to the corresponding volume descriptor. */ + pt_temp = ftello64(viso->tf.file) - pt_start; + p = data; + VISO_LBE_32(p, pt_temp); + viso_pwrite(data, viso->pt_meta_offsets[i >> 1], 8, 1, viso->tf.file); + + /* Pad to the next even sector. */ + write = ftello64(viso->tf.file) % (viso->sector_size * 2); + if (write) { + write = (viso->sector_size * 2) - write; + memset(data, 0x00, write); + fwrite(data, write, 1, viso->tf.file); + } + } + + /* Write directory records for each type. */ + for (int i = 0; i < 2; i++) { + cdrom_image_viso_log("VISO: Generating directory record set #%d:\n", i); + + /* Go through directories. */ + dir = &viso->root_dir; + while (dir) { + /* Pad to the next sector if required. */ + write = ftello64(viso->tf.file) % viso->sector_size; + if (write) { + write = viso->sector_size - write; + memset(data, 0x00, write); + fwrite(data, write, 1, viso->tf.file); + } + + /* Save this directory's child record array's start offset. */ + uint64_t dir_start = ftello64(viso->tf.file); + + /* Write this directory's child record array's sector offset to its record... */ + uint32_t dir_temp = dir_start / viso->sector_size; + p = data; + VISO_LBE_32(p, dir_temp); + viso_pwrite(data, dir->dr_offsets[i] + 2, 8, 1, viso->tf.file); + + /* ...and to its path table entries. */ + viso_pwrite(data, dir->pt_offsets[i << 1] + 2, 4, 1, viso->tf.file); /* little endian */ + viso_pwrite(data + 4, dir->pt_offsets[(i << 1) | 1] + 2, 4, 1, viso->tf.file); /* big endian */ + + if (i) /* clear union if we no longer need path table offsets */ + dir->file = NULL; + + /* Go through entries in this directory. */ + viso_entry_t *entry = dir->first_child; + int dir_type = VISO_DIR_CURRENT; + while (entry) { + cdrom_image_viso_log("[%08X] %s => %s\n", entry, + entry->path ? entry->path : ((dir_type == VISO_DIR_PARENT) ? dir->parent->path : dir->path), + i ? entry->name_rr : entry->name_short); + + /* Fill directory record. */ + viso_fill_dir_record(entry, data, dir_type); + + /* Entries cannot cross sector boundaries, so pad to the next sector if needed. */ + write = viso->sector_size - (ftello64(viso->tf.file) % viso->sector_size); + if (write < data[0]) { + p = data + (viso->sector_size * 2) - write; + memset(p, 0x00, write); + fwrite(p, write, 1, viso->tf.file); + } + + /* Write this entry's record's offset. */ + entry->dr_offsets[i] = ftello64(viso->tf.file); + + /* Write data related to the . and .. pseudo-subdirectories, + while advancing the current directory type. */ + if (dir_type == VISO_DIR_CURRENT) { + /* Write a self-referential pointer to this entry. */ + p = data + 2; + VISO_LBE_32(p, dir_temp); + + dir_type = VISO_DIR_PARENT; + } else if (dir_type == VISO_DIR_PARENT) { + /* Copy the parent directory's offset and size. The root directory's + parent size is a special, self-referential case handled later. */ + viso_pread(data + 2, dir->parent->dr_offsets[i] + 2, 16, 1, viso->tf.file); + + dir_type = i ? VISO_DIR_JOLIET : VISO_DIR_REGULAR; + } + + /* Write entry. */ + fwrite(data, data[0], 1, viso->tf.file); + + /* Move on to the next entry, and stop if the end of this directory was reached. */ + entry = entry->next; + if (entry && (entry->parent != dir)) + break; + } + + /* Write this directory's child record array's size to its parent and . records. */ + dir_temp = ftello64(viso->tf.file) - dir_start; + p = data; + VISO_LBE_32(p, dir_temp); + viso_pwrite(data, dir->dr_offsets[i] + 10, 8, 1, viso->tf.file); + viso_pwrite(data, dir->first_child->dr_offsets[i] + 10, 8, 1, viso->tf.file); + if (dir->parent == dir) /* write size to .. on root directory as well */ + viso_pwrite(data, dir->first_child->next->dr_offsets[i] + 10, 8, 1, viso->tf.file); + + /* Move on to the next directory. */ + dir = dir->next_dir; + } + + /* Pad to the next even sector. */ + write = ftello64(viso->tf.file) % (viso->sector_size * 2); + if (write) { + write = (viso->sector_size * 2) - write; + memset(data, 0x00, write); + fwrite(data, write, 1, viso->tf.file); + } + } + + /* Allocate entry map for sector->file lookups. */ + viso->entry_map = (viso_entry_t **) calloc(viso->entry_map_size, sizeof(viso_entry_t *)); + viso->metadata_sectors = ftello64(viso->tf.file) / viso->sector_size; + viso->all_sectors = viso->metadata_sectors; + + /* Go through files, allocating them to sectors. */ + cdrom_image_viso_log("VISO: Allocating sectors for files (entry map size %d):\n", viso->entry_map_size); + viso_entry_t *prev_entry = &viso->root_dir, + *entry = prev_entry->next, + **entry_map_p = viso->entry_map; + while (entry) { + /* Skip this entry if it corresponds to a directory. */ + if (S_ISDIR(entry->stats.st_mode)) { + /* Deallocate directory entries to save some memory. */ + prev_entry->next = entry->next; + free(entry); + entry = prev_entry->next; + continue; + } + + /* Set this file's starting offset. */ + entry->data_offset = ((uint64_t) viso->all_sectors) * viso->sector_size; + + /* Write this file's starting sector offset to its directory entries. */ + p = data; + VISO_LBE_32(p, viso->all_sectors); + for (int i = 0; i < (sizeof(entry->dr_offsets) / sizeof(entry->dr_offsets[0])); i++) + viso_pwrite(data, entry->dr_offsets[i] + 2, 8, 1, viso->tf.file); + + /* Determine how many sectors this file will take. */ + uint32_t size = entry->stats.st_size / viso->sector_size; + if (entry->stats.st_size % viso->sector_size) + size++; /* round up to the next sector */ + cdrom_image_viso_log("[%08X] %s => %" PRIu32 " + %" PRIu32 " sectors\n", entry, entry->path, viso->all_sectors, size); + + /* Allocate sectors to this file. */ + viso->all_sectors += size; + while (size-- > 0) + *entry_map_p++ = entry; + + /* Move on to the next entry. */ + entry = entry->next; + } + + /* Write final volume size to all volume descriptors. */ + p = data; + VISO_LBE_32(p, viso->all_sectors); + for (int i = 0; i < (sizeof(viso->vol_size_offsets) / sizeof(viso->vol_size_offsets[0])); i++) + viso_pwrite(data, viso->vol_size_offsets[i], 8, 1, viso->tf.file); + + /* Metadata processing is finished, read it back to memory. */ + cdrom_image_viso_log("VISO: Reading back %d sectors of metadata\n", viso->metadata_sectors); + viso->metadata = (uint8_t *) calloc(viso->metadata_sectors, viso->sector_size); + if (!viso->metadata) + goto end; + fseeko64(viso->tf.file, 0, SEEK_SET); + uint64_t metadata_size = viso->metadata_sectors * viso->sector_size, metadata_remain = metadata_size; + while (metadata_remain > 0) + metadata_remain -= fread(viso->metadata + (metadata_size - metadata_remain), 1, MIN(metadata_remain, 2048), viso->tf.file); + + /* We no longer need the temporary file; close and delete it. */ + fclose(viso->tf.file); + viso->tf.file = NULL; +#ifndef ENABLE_CDROM_IMAGE_VISO_LOG + remove(nvr_path(viso->tf.fn)); +#endif + + /* All good. */ + *error = 0; + +end: + /* Set the function pointers. */ + viso->tf.priv = viso; + if (!*error) { + cdrom_image_viso_log("VISO: Initialized\n"); + viso->tf.read = viso_read; + viso->tf.get_length = viso_get_length; + viso->tf.close = viso_close; + return &viso->tf; + } else { + cdrom_image_viso_log("VISO: Initialization failed\n"); + if (data) + free(data); + if (wtemp) + free(wtemp); + viso_close(&viso->tf); + return NULL; + } +} diff --git a/src/include/86box/cdrom_image_backend.h b/src/include/86box/cdrom_image_backend.h index 6fe26d1e3..86af9f682 100644 --- a/src/include/86box/cdrom_image_backend.h +++ b/src/include/86box/cdrom_image_backend.h @@ -52,6 +52,7 @@ typedef struct { char fn[260]; FILE *file; + void *priv; } track_file_t; typedef struct { @@ -89,5 +90,11 @@ extern int cdi_load_cue(cd_img_t *cdi, const char *cuefile); extern int cdi_has_data_track(cd_img_t *cdi); extern int cdi_has_audio_track(cd_img_t *cdi); +/* Virtual ISO functions. */ +extern int viso_read(void *p, uint8_t *buffer, uint64_t seek, size_t count); +extern uint64_t viso_get_length(void *p); +extern void viso_close(void *p); +extern track_file_t *viso_init(const char *dirname, int *error); + #endif /*CDROM_IMAGE_BACKEND_H*/ diff --git a/src/win/Makefile.mingw b/src/win/Makefile.mingw index 325c5d5dd..44f0807db 100644 --- a/src/win/Makefile.mingw +++ b/src/win/Makefile.mingw @@ -618,7 +618,7 @@ MINIVHDOBJ := cwalk.o libxml2_encoding.o minivhd_convert.o \ minivhd_struct_rw.o minivhd_util.o CDROMOBJ := cdrom.o \ - cdrom_image_backend.o cdrom_image.o + cdrom_image_backend.o cdrom_image_viso.o cdrom_image.o ZIPOBJ := zip.o