/* * 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. * * Implementation of the pcjs v2 floppy image format (read-only) * * Authors: cold-brewed * * Copyright 2024 cold-brewed * * More info: https://www.pcjs.org/tools/diskimage/ * pcjs disk module v2: https://github.com/jeffpar/pcjs/blob/master/machines/pcx86/modules/v2/disk.js */ #ifndef EMU_FLOPPY_PCJS_H #define EMU_FLOPPY_PCJS_H /* Currently targeting v2 of the spec */ #define PCJS_DISK_SPEC_VERSION 2 #define PCJS_MAX_TRACKS 256 #define PCJS_MAX_SIDES 2 #define PCJS_MAX_SECTORS 256 /* The json keys as defined in each sector array item */ #define PCJS_OBJECT_KEY_CYLINDER "c" #define PCJS_OBJECT_KEY_TRACK PCJS_OBJECT_KEY_CYLINDER #define PCJS_OBJECT_KEY_HEAD "h" #define PCJS_OBJECT_KEY_SECTOR "s" #define PCJS_OBJECT_KEY_LENGTH "l" #define PCJS_OBJECT_KEY_DATA "d" #define PCJS_OBJECT_KEY_FILE "f" #define PCJS_OBJECT_KEY_OFFSET "d" /* The json keys as defined in the fileTable object */ #define PCJS_OBJECT_KEY_FT_HASH "hash" #define PCJS_OBJECT_KEY_FT_PATH "path" #define PCJS_OBJECT_KEY_FT_ATTR "attr" #define PCJS_OBJECT_KEY_FT_DATE "date" #define PCJS_OBJECT_KEY_FT_SIZE "size" /* String length defaults */ #define PCJS_IMAGE_INFO_STRING_LEN 128 #define PCJS_IMAGE_INFO_ARRAY_LEN 128 #define PCJS_FILE_TABLE_STRING_LEN 128 /* Defaults for optional json values */ #define JSON_OPTIONAL_NUMBER_DEFAULT 0 #define JSON_OPTIONAL_STRING_DEFAULT "" /* Structure for each sector */ typedef struct pcjs_sector_t { /* Track number */ uint8_t track; /* Side number */ uint8_t side; /* Sector number */ uint8_t sector; /* Size of the sector */ uint16_t size; /* Encoded size of the sector */ uint16_t encoded_size; /* Pointer the the allocated data for the sector */ uint8_t *data; /* Number of times to repeat the pattern until end of sector */ uint16_t pattern_repeat; /* Last pattern entry to repeat */ int32_t last_entry; /* Maps back to a file entry. -1 if not set */ int32_t file; /* The offset in the mapped file entry. -1 if not set */ int32_t offset; } pcjs_sector_t; /* Cases are mixed here (some camelCase) to match the pcjs values */ typedef struct pcjs_image_info_t { char type[PCJS_IMAGE_INFO_STRING_LEN]; char name[PCJS_IMAGE_INFO_STRING_LEN]; char format[PCJS_IMAGE_INFO_STRING_LEN]; char hash[PCJS_IMAGE_INFO_STRING_LEN]; uint32_t checksum; uint8_t cylinders; uint8_t heads; uint8_t trackDefault; uint16_t sectorDefault; uint32_t diskSize; uint8_t boot_sector[PCJS_IMAGE_INFO_ARRAY_LEN]; uint8_t boot_sector_array_size; char version[PCJS_IMAGE_INFO_STRING_LEN]; char repository[PCJS_IMAGE_INFO_STRING_LEN]; } pcjs_image_info_t; typedef struct pcjs_file_table_entry_t { char hash[PCJS_FILE_TABLE_STRING_LEN]; char path[PCJS_FILE_TABLE_STRING_LEN]; char attr[PCJS_FILE_TABLE_STRING_LEN]; char date[PCJS_FILE_TABLE_STRING_LEN]; uint32_t size; } pcjs_file_table_entry_t ; typedef struct pcjs_file_table_t { pcjs_file_table_entry_t *entries; uint16_t num_entries; } pcjs_file_table_t; typedef struct pcjs_t { /* FILE pointer for the json file */ FILE *fp; /* These values are read in from the metadata */ /* Total number of tracks */ uint8_t total_tracks; /* Total number of sides */ uint8_t total_sides; /* Total number of sectors per track */ uint16_t total_sectors; /* These values are calculated for validation */ /* Calculated number of tracks */ uint8_t calc_total_tracks; /* Calculated number of sides */ uint8_t calc_total_sides; /* Calculated number of sectors per track */ uint16_t calc_total_sectors; /* Number of sectors per track */ uint8_t spt[PCJS_MAX_TRACKS][PCJS_MAX_SIDES]; /* Current track */ uint8_t current_track; /* Current side */ uint8_t current_side; /* Current sector */ uint8_t current_sector[PCJS_MAX_SIDES]; /* Disk is in dmf format? */ uint8_t dmf; uint8_t interleave; uint8_t gap2_len; uint8_t gap3_len; int track_width; /* Flags for the entire disk */ uint16_t disk_flags; /* Flags for the current track */ uint16_t track_flags; uint8_t interleave_ordered[PCJS_MAX_TRACKS][PCJS_MAX_SIDES]; /* The main mapping of all the sectors back to each individual pcjs_sector_t item. */ pcjs_sector_t sectors[PCJS_MAX_TRACKS][PCJS_MAX_SIDES][PCJS_MAX_SECTORS]; /* Disk metadata information contained in each image */ pcjs_image_info_t image_info; /* Optional file table mapping for each sector */ pcjs_file_table_t file_table; } pcjs_t; /* Errors */ enum pcjs_img_error { E_SUCCESS = 0, E_MISSING_KEY = 1, E_UNEXPECTED_VALUE = 2, E_INTEGRITY, E_INVALID_OBJECT, E_ALLOC, E_PARSE, }; typedef enum pcjs_img_error pcjs_error_t; /* Macros */ /* Macro for getting image info metadata: strings */ #define IMAGE_INFO_GET_STRING(type) \ const cJSON * type##_json = cJSON_GetObjectItemCaseSensitive(imageInfo, #type); \ if (cJSON_IsString( type##_json) && type##_json->valuestring != NULL) { \ strncpy(dev->image_info.type, type##_json->valuestring, sizeof(dev->image_info. type) - 1); \ } else { \ pcjs_log("Required string value for \"%s\" missing from imageInfo\n", #type); \ pcjs_error = E_INVALID_OBJECT; \ return 1; \ } /* Macro for getting image info metadata: ints */ #define IMAGE_INFO_GET_NUMBER(type) \ const cJSON * type##_json = cJSON_GetObjectItemCaseSensitive(imageInfo, #type); \ if (cJSON_IsNumber( type##_json)) { \ dev->image_info.type = type##_json->valueint; \ } else { \ pcjs_log("Required number value for \"%s\" missing from imageInfo\n", #type); \ pcjs_error = E_INVALID_OBJECT; \ return 1; \ } /* Macro for getting required object value: number */ #define JSON_GET_OBJECT_NUMBER_REQUIRED(var, json, key) \ const cJSON *var##_json = cJSON_GetObjectItemCaseSensitive(json, key); \ if (!cJSON_IsNumber(var##_json)) { \ pcjs_log("Required number value for \"%s\" missing or invalid\n", key); \ pcjs_error = E_INVALID_OBJECT; \ goto fail; \ } else { \ var = var##_json->valueint; \ } /* Macro for getting optional object value: number * Default value will be used if the number does not exist */ #define JSON_GET_OBJECT_NUMBER_OPTIONAL(var, json, key) \ const cJSON *var##_json = cJSON_GetObjectItemCaseSensitive(json, key); \ if (!cJSON_IsNumber(var##_json)) { \ var = JSON_OPTIONAL_NUMBER_DEFAULT; \ } else { \ var = var##_json->valueint; \ } /* Macro for getting optional object value: number * Provided default value will be used if the number does not exist */ #define JSON_GET_OBJECT_NUMBER_OPTIONAL_DEFAULT(var, json, key, default) \ const cJSON *var##_json = cJSON_GetObjectItemCaseSensitive(json, key); \ if (!cJSON_IsNumber(var##_json)) { \ var = default; \ } else { \ var = var##_json->valueint; \ } /* Macro for getting optional object value: string * Default value will be used if the string does not exist */ #define JSON_GET_OBJECT_STRING_OPTIONAL(var, json, key) \ const cJSON * var##_json = cJSON_GetObjectItemCaseSensitive(json, key); \ if (cJSON_IsString( var##_json) && var##_json->valuestring != NULL) { \ strncpy(var, var##_json->valuestring, sizeof(var) - 1); \ } else { \ strncpy(var, JSON_OPTIONAL_STRING_DEFAULT, sizeof(var) - 1); \ } /* Macro for getting required object value: string */ #define JSON_GET_OBJECT_STRING_REQUIRED(var, json, key) \ const cJSON * var##_json = cJSON_GetObjectItemCaseSensitive(json, key); \ if (cJSON_IsString( var##_json) && var##_json->valuestring != NULL) { \ strncpy(var, var##_json->valuestring, sizeof(var) - 1); \ } else { \ pcjs_error = E_INVALID_OBJECT; \ goto fail; \ } extern void pcjs_init(void); extern void pcjs_load(int drive, char *fn); extern void pcjs_close(int drive); extern const char* pcjs_errmsg(void); #endif