diff --git a/display/display.c b/display/display.c index 5ad0691..22efa6e 100644 --- a/display/display.c +++ b/display/display.c @@ -975,6 +975,18 @@ display_init(enum display_type type, int sf, void *dptr) fprintf(stderr, "Display initialization failed\r\n"); return 0; } + +void +display_close(void *dptr) +{ + if (!initialized) + return; + + free (points); + ws_shutdown(); + + initialized = 0; +} void display_reset(void) diff --git a/display/display.h b/display/display.h index 391bf4e..239d102 100644 --- a/display/display.h +++ b/display/display.h @@ -65,6 +65,11 @@ enum display_type { */ extern int display_init(enum display_type, int scale, void *dptr); +/* + * close display + */ +extern void display_close(void *dptr); + /* return size of virtual display */ extern int display_xpoints(void); extern int display_ypoints(void); diff --git a/sim_disk.c b/sim_disk.c index a66dc1c..346a8de 100644 --- a/sim_disk.c +++ b/sim_disk.c @@ -1241,12 +1241,10 @@ if (Scb->scb_b_bitmapblks < 127) ret_val = (((t_offset)Scb->scb_r_blocks[Scb->scb_b_bitmapblks].scb_w_freeblks << 16) + Scb->scb_r_blocks[Scb->scb_b_bitmapblks].scb_w_freeptr) * 512; else ret_val = (((t_offset)Scb->scb_r_blocks[0].scb_w_freeblks << 16) + Scb->scb_r_blocks[0].scb_w_freeptr) * 512; -if (!sim_quiet) { - sim_printf ("%s%d: '%s' Contains an ODS1 File system\n", sim_dname (dptr), (int)(uptr-dptr->units), uptr->filename); - sim_printf ("%s%d: Volume Name: %12.12s ", sim_dname (dptr), (int)(uptr-dptr->units), Home.hm1_t_volname); - sim_printf ("Format: %12.12s ", Home.hm1_t_format); - sim_printf ("Sectors In Volume: %u\n", (uint32)(ret_val / 512)); - } +sim_messagef (SCPE_OK, "%s%d: '%s' Contains an ODS1 File system\n", sim_dname (dptr), (int)(uptr-dptr->units), uptr->filename); +sim_messagef (SCPE_OK, "%s%d: Volume Name: %12.12s ", sim_dname (dptr), (int)(uptr-dptr->units), Home.hm1_t_volname); +sim_messagef (SCPE_OK, "Format: %12.12s ", Home.hm1_t_format); +sim_messagef (SCPE_OK, "Sectors In Volume: %u\n", (uint32)(ret_val / 512)); Return_Cleanup: uptr->capac = saved_capac; @@ -1297,10 +1295,8 @@ for (i = 0; i < 8; i++) { max_lbn_partnum = i; } } -if (!sim_quiet) { - sim_printf ("%s%d: '%s' Contains Ultrix partitions\n", sim_dname (dptr), (int)(uptr-dptr->units), uptr->filename); - sim_printf ("Partition with highest sector: %c, Sectors On Disk: %u\n", 'a' + max_lbn_partnum, max_lbn); - } +sim_messagef (SCPE_OK, "%s%d: '%s' Contains Ultrix partitions\n", sim_dname (dptr), (int)(uptr-dptr->units), uptr->filename); +sim_messagef (SCPE_OK, "Partition with highest sector: %c, Sectors On Disk: %u\n", 'a' + max_lbn_partnum, max_lbn); ret_val = ((t_offset)max_lbn) * 512; Return_Cleanup: @@ -1308,6 +1304,475 @@ uptr->capac = saved_capac; return ret_val; } +#pragma pack(push,1) +/* + * The first logical block of device cluster 1 is either: + * 1. MFD label entry (RSTS versions through 7.x) + * 2. Disk Pack label (RSTS version 8.0 and later) + */ +typedef struct _RSTS_MFDLABEL { + uint16 ml_ulnk; + uint16 ml_mbm1; + uint16 ml_reserved1; + uint16 ml_reserved2; + uint16 ml_pcs; + uint16 ml_pstat; + uint16 ml_packid[2]; + } RSTS_MFDLABEL; + +typedef struct _RSTS_PACKLABEL { + uint16 pk_mb01; + uint16 pk_mbm1; + uint16 pk_mdcn; + uint16 pk_plvl; +#define PK_LVL0 0000 +#define PK_LVL11 0401 +#define PK_LVL12 0402 + uint16 pk_ppcs; + uint16 pk_pstat; +#define PK_UC_NEW 0020000 + uint16 pk_packid[2]; + uint16 pk_tapgvn[2]; + uint16 pk_bckdat; + uint16 pk_bcktim; + } RSTS_PACKLABEL; + +typedef union _RSTS_ROOT { + RSTS_MFDLABEL rt_mfd; + RSTS_PACKLABEL rt_pack; + uint8 rt_block[512]; + } RSTS_ROOT; + +typedef struct _RSTS_MFDBLOCKETTE { + uint16 mb_ulnk; + uint16 mb_mbm1; + uint16 mb_reserved1; + uint16 mb_reserved2; + uint16 mb_reserved3; + uint16 mb_malnk; + uint16 mb_lppn; + uint16 mb_lid; +#define MB_ID 0051064 + } RSTS_MFDBLOCKETTE; +#define IS_VALID_RSTS_MFD(b) \ + ((((b)->mb_ulnk == 0) || ((b)->mb_ulnk == 1)) && \ + ((b)->mb_mbm1 == 0177777) && \ + ((b)->mb_reserved1 == 0) && \ + ((b)->mb_reserved2 == 0) && \ + ((b)->mb_reserved3 == 0) && \ + ((b)->mb_lppn == 0177777) && \ + ((b)->mb_lid == MB_ID)) + +typedef struct _RSTS_GFDBLOCKETTE { + uint16 gb_ulnk; + uint16 gb_mbm1; + uint16 gb_reserved1; + uint16 gb_reserved2; + uint16 gb_reserved3; + uint16 gb_reserved4; + uint16 gb_lppn; + uint16 gb_lid; +#define GB_ID 0026264 + } RSTS_GFDBLOCKETTE; +#define IS_VALID_RSTS_GFD(b, g) \ + ((((b)->gb_ulnk == 0) || ((b)->gb_ulnk == 1)) && \ + ((b)->gb_mbm1 == 0177777) && \ + ((b)->gb_reserved1 == 0) && \ + ((b)->gb_reserved2 == 0) && \ + ((b)->gb_reserved3 == 0) && \ + ((b)->gb_reserved4 == 0) && \ + ((b)->gb_lppn == (((g) << 8) | 0377)) && \ + ((b)->gb_lid == GB_ID)) + +typedef struct _RSTS_UFDBLOCKETTE { + uint16 ub_ulnk; + uint16 ub_mbm1; + uint16 ub_reserved1; + uint16 ub_reserved2; + uint16 ub_reserved3; + uint16 ub_reserved4; + uint16 ub_lppn; + uint16 ub_lid; +#define UB_ID 0102064 + } RSTS_UFDBLOCKETTE; +#define IS_VALID_RSTS_UFD(b, g, u) \ + (((b)->ub_mbm1 == 0177777) && \ + ((b)->ub_reserved1 == 0) && \ + ((b)->ub_reserved2 == 0) && \ + ((b)->ub_reserved3 == 0) && \ + ((b)->ub_reserved4 == 0) && \ + ((b)->ub_lppn == (((g) << 8) | (u))) && \ + ((b)->ub_lid == UB_ID)) + +typedef struct _RSTS_UNAME { + uint16 un_ulnk; + uint16 un_unam; + uint16 un_reserved1; + uint16 un_reserved2; + uint16 un_ustat; + uint16 un_uacnt; + uint16 un_uaa; + uint16 un_uar; + } RSTS_UNAME; + +typedef struct _RSTS_FNAME { + uint16 fn_ulnk; + uint16 fn_unam[3]; + uint16 fn_ustat; + uint16 fn_uacnt; + uint16 fn_uaa; + uint16 fn_uar; + } RSTS_FNAME; + +typedef struct _RSTS_ACNT { + uint16 ac_ulnk; + uint16 ac_udla; + uint16 ac_usiz; + uint16 ac_udc; + uint16 ac_utc; + uint16 ac_urts[2]; + uint16 ac_uclus; + } RSTS_ACNT; + +typedef struct _RSTS_RETR { + uint16 rt_ulnk; + uint16 rt_uent[7]; +#define RT_ENTRIES 7 + } RSTS_RETR; + +typedef struct _RSTS_DCMAP { + uint16 dc_clus; +#define DC_MASK 0077777 + uint16 dc_map[7]; + } RSTS_DCMAP; + +/* + * Directory link definitions + */ +#define DL_USE 0000001 +#define DL_BAD 0000002 +#define DL_CHE 0000004 +#define DL_CLN 0000010 +#define DL_ENO 0000760 +#define DL_CLO 0007000 +#define DL_BLO 0170000 + +#define DLSH_ENO 4 +#define DLSH_CLO 9 +#define DLSH_BLO 12 + +#define BLOCKETTE_SZ (8 * sizeof(uint16)) +#define MAP_OFFSET (31 * BLOCKETTE_SZ) + +#define SATT0 0073374 +#define SATT1 0076400 +#define SATT2 0075273 + +#pragma pack(pop) + +typedef struct _rstsContext { + UNIT *uptr; + int dcshift; + int pcs; + char packid[8]; + t_seccnt sects; + RSTS_DCMAP map; +} rstsContext; + +static char rad50[] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ$.%0123456789"; + +static void r50Asc(uint16 val, char *buf) +{ +buf[2] = rad50[val % 050]; +val /= 050; +buf[1] = rad50[val % 050]; +buf[0] = rad50[val / 050]; +} + +static t_stat rstsReadBlock(rstsContext *context, uint16 cluster, uint16 block, void *buf) +{ +t_lba blk = (cluster << context->dcshift) + block; +t_seccnt sects_read; + +if ((sim_disk_rdsect(context->uptr, blk * context->sects, (uint8 *)buf, §s_read, context->sects) == SCPE_OK) && + (sects_read == context->sects)) + return SCPE_OK; + +return SCPE_IOERR; +} + +static t_stat rstsReadBlockette(rstsContext *context, uint16 link, void *buf) +{ +uint16 block = (link & DL_BLO) >> DLSH_BLO; +uint16 dcn = (link & DL_CLO) >> DLSH_CLO; +uint16 blockette = (link & DL_ENO) >> DLSH_ENO; +uint8 temp[512]; + +if ((dcn != 7) && (blockette != 31) && + (block <= (context->map.dc_clus & DC_MASK))) { + if (rstsReadBlock(context, context->map.dc_map[dcn], block, temp) == SCPE_OK) { + memcpy(buf, &temp[blockette * BLOCKETTE_SZ], BLOCKETTE_SZ); + return SCPE_OK; + } + } +return SCPE_IOERR; +} + +static t_stat rstsFind01UFD(rstsContext *context, uint16 *ufd, uint16 *level) +{ +uint16 dcs = 1 << context->dcshift; +RSTS_ROOT root; +uint16 buf[256]; + +if (rstsReadBlock(context, 1, 0, &root) == SCPE_OK) { + /* + * First validate fields which are common to both the MFD label and + * Pack label - we'll use Pack label offsets here. + */ + if ((root.rt_pack.pk_mbm1 == 0177777) && (root.rt_pack.pk_ppcs >= dcs)) { + char ch, *tmp = &context->packid[1]; + uint16 mfd, gfd; + + context->pcs = root.rt_pack.pk_ppcs; + + r50Asc(root.rt_pack.pk_packid[0], &context->packid[0]); + r50Asc(root.rt_pack.pk_packid[1], &context->packid[3]); + context->packid[6] = '\0'; + + /* + * The Pack ID must consist of 1 - 6 alphanumeric characters + * padded at the end with spaces. + */ + if (!isalnum(context->packid[0])) + return SCPE_IOERR; + + while ((ch = *tmp++) != 0) { + if (!isalnum(ch)) { + if (ch != ' ') + return SCPE_IOERR; + + while (*tmp) + if (*tmp++ != ' ') + return SCPE_IOERR; + break; + } + } + + /* + * Determine the pack revision level and, therefore, the path to + * [0,1]satt.sys which will allow us to determine the size of the + * pack used by RSTS. + */ + if ((root.rt_pack.pk_pstat & PK_UC_NEW) == 0) { + uint16 link = root.rt_mfd.ml_ulnk; + RSTS_UNAME uname; + + /* + * Old format used by RSTS up through V07.x + */ + if (dcs > 16) + return SCPE_IOERR; + + *level = PK_LVL0; + + memcpy(&context->map, &root.rt_block[MAP_OFFSET], BLOCKETTE_SZ); + + /* + * Scan the MFD name entries looking for [0,1]. Note there will + * always be at least 1 entry. + */ + do { + if (rstsReadBlockette(context, link, &uname) != SCPE_OK) + break; + + if (uname.un_unam == ((0 << 8) | 1)) { + *ufd = uname.un_uar; + return SCPE_OK; + } + } while ((link = uname.un_ulnk) != 0); + } + else { + /* + * New format used by RSTS V08 and later + */ + switch (root.rt_pack.pk_plvl) { + case PK_LVL11: + if (dcs > 16) + return SCPE_IOERR; + break; + + case PK_LVL12: + if (dcs > 64) + return SCPE_IOERR; + break; + + default: + return SCPE_IOERR; + } + *level = root.rt_pack.pk_plvl; + + mfd = root.rt_pack.pk_mdcn; + + if (rstsReadBlock(context, mfd, 0, buf) == SCPE_OK) { + if (IS_VALID_RSTS_MFD((RSTS_MFDBLOCKETTE *)buf)) { + if (rstsReadBlock(context, mfd, 1, buf) == SCPE_OK) + if ((gfd = buf[0]) != 0) + if (rstsReadBlock(context, gfd, 0, buf) == SCPE_OK) + if (IS_VALID_RSTS_GFD((RSTS_GFDBLOCKETTE *)buf, 0)) { + if (rstsReadBlock(context, gfd, 1, buf) == SCPE_OK) + if ((*ufd = buf[1]) != 0) + return SCPE_OK; + } + } + } + } + } + } +return SCPE_IOERR; +} + +static t_stat rstsLoadAndScanSATT(rstsContext *context, uint16 uaa, uint16 uar, t_offset *result) +{ +t_offset blocks = 0; +uint8 bitmap[8192]; +int i, j; +RSTS_ACNT acnt; +RSTS_RETR retr; + +if (uar != 0) { + if (rstsReadBlockette(context, uaa, &acnt) == SCPE_OK) { + uint16 blocks = acnt.ac_usiz; + uint16 offset = 0; + + memset(bitmap, 0xFF, sizeof(bitmap)); + + if (blocks != 0) { + do { + int i, j; + uint16 fcl; + + if (rstsReadBlockette(context, uar, &retr) != SCPE_OK) + return SCPE_IOERR; + + for (i = 0; i < RT_ENTRIES; i++) { + if ((fcl = retr.rt_uent[i]) == 0) + goto scanBitmap; + + for (j = 0; j < acnt.ac_uclus; j++) { + if ((blocks == 0) || (offset >= sizeof(bitmap))) + goto scanBitmap; + + if (rstsReadBlock(context, fcl, j, &bitmap[offset]) != SCPE_OK) + return SCPE_IOERR; + + offset += 512; + blocks--; + } + } + } while ((uar = retr.rt_ulnk) != 0); + + scanBitmap: + for (i = sizeof(bitmap) - 1; i != 0; i--) + if (bitmap[i] != 0xFF) { + blocks = i * 8; + for (j = 7; j >= 0; j--) + if ((bitmap[i] & (1 << j)) == 0) { + blocks += j + 1; + goto scanDone; + } + } + scanDone: + *result = (blocks + 1) * context->pcs; + return SCPE_OK; + } + } + } +return SCPE_IOERR; +} + +static t_offset get_rsts_filesystem_size (UNIT *uptr) +{ +DEVICE *dptr; +t_addr saved_capac; +struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx; +t_addr temp_capac = (sim_toffset_64 ? (t_addr)0xFFFFFFFFu : (t_addr)0x7FFFFFFFu); /* Make sure we can access the largest sector */ +uint8 buf[512]; +t_offset ret_val = (t_offset)-1; +rstsContext context; + +if ((dptr = find_dev_from_unit (uptr)) == NULL) + return ret_val; +saved_capac = uptr->capac; +uptr->capac = temp_capac; + +context.uptr = uptr; +context.sects = 512 / ctx->sector_size; + +/* + * Check all possible device cluster sizes + */ +for (context.dcshift = 0; context.dcshift < 8; context.dcshift++) { + uint16 ufd, level; + + /* + * We need to find [0,1]SATT.SYS to compute the actual size of the disk. + * First find the DCN of the [0,1] UFD. + */ + if (rstsFind01UFD(&context, &ufd, &level) == SCPE_OK) { + if (rstsReadBlock(&context, ufd, 0, buf) == SCPE_OK) { + if (IS_VALID_RSTS_UFD((RSTS_UFDBLOCKETTE *)buf, 0, 1)) { + uint16 link = ((RSTS_UFDBLOCKETTE *)buf)->ub_ulnk; + RSTS_FNAME fname; + + memcpy(&context.map, &buf[MAP_OFFSET], BLOCKETTE_SZ); + + /* + * Scan the UFD looking for SATT.SYS - the allocation bitmap + */ + do { + if (rstsReadBlockette(&context, link, &fname) != SCPE_OK) + break; + + if ((fname.fn_unam[0] == SATT0) && + (fname.fn_unam[1] == SATT1) && + (fname.fn_unam[2] == SATT2)) { + if (rstsLoadAndScanSATT(&context, fname.fn_uaa, fname.fn_uar, &ret_val) == SCPE_OK) { + const char *fmt = "???"; + + ret_val *= 512; + + switch (level) { + case PK_LVL0: + fmt = "0.0"; + break; + + case PK_LVL11: + fmt = "1.1"; + break; + + case PK_LVL12: + fmt = "1.2"; + break; + } + + sim_messagef(SCPE_OK, "%s%d: '%s' Contains a RSTS File system\n", sim_dname (dptr), (int)(uptr-dptr->units), uptr->filename); + sim_messagef(SCPE_OK, "%s%d: Pack ID: %6.6s ", sim_dname (dptr), (int)(uptr-dptr->units), context.packid); + sim_messagef(SCPE_OK, "Revision Level: %3s ", fmt); + sim_messagef(SCPE_OK, "Pack Clustersize: %d\n", context.pcs); + sim_messagef(SCPE_OK, "%s%d: Last Unallocated Sector In File System: %u\n", sim_dname (dptr), (int)(uptr-dptr->units), (uint32)(ret_val / 512)); + goto cleanup_done; + } + } + } while ((link = fname.fn_ulnk) != 0); + } + } + } + } +cleanup_done: +uptr->capac = saved_capac; +return ret_val; +} + #pragma pack(push,1) typedef struct _RT11_HomeBlock { uint8 hb_b_bbtable[130]; @@ -1484,29 +1949,27 @@ Next_Partition: Return_Cleanup: if (partitions) { - if (!sim_quiet) { - const char *parttype; + const char *parttype; - switch (version) { - case HB_C_SYSVER_V3A: - parttype = "V3A"; - break; + switch (version) { + case HB_C_SYSVER_V3A: + parttype = "V3A"; + break; - case HB_C_SYSVER_V04: - parttype = "V04"; - break; + case HB_C_SYSVER_V04: + parttype = "V04"; + break; - case HB_C_SYSVER_V05: - parttype = "V05"; - break; + case HB_C_SYSVER_V05: + parttype = "V05"; + break; - default: - parttype = "???"; - break; - } - sim_printf ("%s%d: '%s' Contains RT11 partitions\n", sim_dname (dptr), (int)(uptr-dptr->units), uptr->filename); - sim_printf ("%d valid partition%s, Type: %s, Sectors On Disk: %u\n", partitions, partitions == 1 ? "" : "s", parttype, (uint32)(ret_val / 512)); + default: + parttype = "???"; + break; } + sim_messagef (SCPE_OK, "%s%d: '%s' Contains RT11 partitions\n", sim_dname (dptr), (int)(uptr-dptr->units), uptr->filename); + sim_messagef (SCPE_OK, "%d valid partition%s, Type: %s, Sectors On Disk: %u\n", partitions, partitions == 1 ? "" : "s", parttype, (uint32)(ret_val / 512)); } uptr->capac = saved_capac; return ret_val; @@ -1520,6 +1983,7 @@ static FILESYSTEM_CHECK checks[] = { &get_ods2_filesystem_size, &get_ods1_filesystem_size, &get_ultrix_filesystem_size, + &get_rsts_filesystem_size, &get_rt11_filesystem_size, /* This should be the last entry in the table to reduce the possibility of matching an RT-11 @@ -2129,19 +2593,36 @@ fprintf (st, "was created. This metadata is therefore available whenever that V fprintf (st, "attached to an emulated disk device in the future so the device type and\n"); fprintf (st, "size can be automatically be configured.\n\n"); -if (0 == (uptr-dptr->units)) { - if (dptr->numunits > 1) { - uint32 i; +if (dptr->numunits > 1) { + uint32 i, attachable_count = 0, out_count = 0, skip_count; - for (i=0; i < dptr->numunits; ++i) - if (dptr->units[i].flags & UNIT_ATTABLE) - fprintf (st, " sim> ATTACH {switches} %s%d diskfile\n", dptr->name, i); + for (i=0; i < dptr->numunits; ++i) + if ((dptr->units[i].flags & UNIT_ATTABLE) && + !(dptr->units[i].flags & UNIT_DIS)) + ++attachable_count; + for (i=0; (i < dptr->numunits) && (out_count < 2); ++i) + if ((dptr->units[i].flags & UNIT_ATTABLE) && + !(dptr->units[i].flags & UNIT_DIS)) { + fprintf (st, " sim> ATTACH {switches} %s%d diskfile\n", dptr->name, i); + ++out_count; + } + if (attachable_count > 4) { + fprintf (st, " .\n"); + fprintf (st, " .\n"); + fprintf (st, " .\n"); } - else - fprintf (st, " sim> ATTACH {switches} %s diskfile\n", dptr->name); + skip_count = attachable_count - 2; + for (i=0; i < dptr->numunits; ++i) + if ((dptr->units[i].flags & UNIT_ATTABLE) && + !(dptr->units[i].flags & UNIT_DIS)) { + if (skip_count == 0) + fprintf (st, " sim> ATTACH {switches} %s%d diskfile\n", dptr->name, i); + else + --skip_count; + } } else - fprintf (st, " sim> ATTACH {switches} %s diskfile\n\n", dptr->name); + fprintf (st, " sim> ATTACH {switches} %s diskfile\n", dptr->name); fprintf (st, "\n%s attach command switches\n", dptr->name); fprintf (st, " -R Attach Read Only.\n"); fprintf (st, " -E Must Exist (if not specified an attempt to create the indicated\n"); @@ -2183,9 +2664,7 @@ fprintf (st, " sim> att rq2 -f vhd RA92.vhd\n"); fprintf (st, " RQ2: creating new file\n"); fprintf (st, " sim> sho rq2\n"); fprintf (st, " RQ2, 1505MB, attached to RA92.vhd, write enabled, RA92, autosize, VHD format\n"); -fprintf (st, " sim> ! dir RA92.vhd\n"); -fprintf (st, " Volume in drive H is New Volume\n"); -fprintf (st, " Volume Serial Number is F8DE-510C\n\n"); +fprintf (st, " sim> dir RA92.vhd\n"); fprintf (st, " Directory of H:\\Data\n\n"); fprintf (st, " 04/14/2011 12:57 PM 5,120 RA92.vhd\n"); fprintf (st, " 1 File(s) 5,120 bytes\n"); @@ -2196,9 +2675,7 @@ fprintf (st, " RQ3: Copied 1505MB. 99%% complete.\n"); fprintf (st, " RQ3: Copied 1505MB. Done.\n"); fprintf (st, " sim> sh rq3\n"); fprintf (st, " RQ3, 1505MB, attached to RA92-1.vhd, write enabled, RA92, autosize, VHD format\n"); -fprintf (st, " sim> ! dir RA92*\n"); -fprintf (st, " Volume in drive H is New Volume\n"); -fprintf (st, " Volume Serial Number is F8DE-510C\n\n"); +fprintf (st, " sim> dir RA92*\n"); fprintf (st, " Directory of H:\\Data\n\n"); fprintf (st, " 04/14/2011 01:12 PM 5,120 RA92-1.vhd\n"); fprintf (st, " 04/14/2011 12:58 PM 5,120 RA92.vhd\n"); diff --git a/sim_rev.h b/sim_rev.h index 02bc902..fe4a642 100644 --- a/sim_rev.h +++ b/sim_rev.h @@ -48,6 +48,14 @@ #include ".git-commit-id.h" #endif +/* + Simh's git commit id would be undefined when working with an + extracted archive (zip file or tar ball). To address this + problem and record the commit id that the archive was created + from, the archive creation process populates the below + information as a consequence of the "sim_rev.h export-subst" + line in the .gitattributes file. + */ #if !defined(SIM_GIT_COMMIT_ID) #define SIM_GIT_COMMIT_ID $Format:%H$ #define SIM_GIT_COMMIT_TIME $Format:%aI$ diff --git a/sim_video.c b/sim_video.c index fe9d6cb..a4fedf4 100644 --- a/sim_video.c +++ b/sim_video.c @@ -37,6 +37,10 @@ t_bool vid_mouse_b1 = FALSE; t_bool vid_mouse_b2 = FALSE; t_bool vid_mouse_b3 = FALSE; static VID_QUIT_CALLBACK vid_quit_callback = NULL; +static VID_GAMEPAD_CALLBACK motion_callback[10]; +static VID_GAMEPAD_CALLBACK button_callback[10]; +static int vid_gamepad_inited = 0; +static int vid_gamepad_ok = 0; /* Or else just joysticks. */ t_stat vid_register_quit_callback (VID_QUIT_CALLBACK callback) { @@ -44,6 +48,41 @@ vid_quit_callback = callback; return SCPE_OK; } +static t_stat register_callback (void **array, int n, void *callback) +{ + int i, j = -1; + + if (!vid_gamepad_inited) { + return SCPE_NOATT; + } + + for (i = 0; i < n; i++) { + if (array[i] == callback) + return SCPE_ALATT; + if (array[i] == NULL) + j = i; + } + + if (j != -1) { + array[j] = callback; + return SCPE_OK; + } + + return SCPE_NXM; +} + +t_stat vid_register_gamepad_motion_callback (VID_GAMEPAD_CALLBACK callback) +{ + int n = sizeof (motion_callback) / sizeof (callback); + return register_callback ((void **)motion_callback, n, (void *)callback); +} + +t_stat vid_register_gamepad_button_callback (VID_GAMEPAD_CALLBACK callback) +{ + int n = sizeof (button_callback) / sizeof (callback); + return register_callback ((void **)button_callback, n, (void *)callback); +} + t_stat vid_show (FILE* st, DEVICE *dptr, UNIT* uptr, int32 val, CONST char* desc) { return vid_show_video (st, uptr, val, desc); @@ -510,6 +549,64 @@ return SCPE_OK; } #endif +static t_stat vid_init_controllers (void) +{ + SDL_Joystick *y; + SDL_version ver; + int i, n; + + if (vid_gamepad_inited) + return SCPE_OK; + + /* Chech that the SDL_GameControllerFromInstanceID function is + available at run time. */ + SDL_GetVersion(&ver); + vid_gamepad_ok = (ver.major > 2 || + (ver.major == 2 && (ver.minor > 0 || ver.patch >= 4))); + + SDL_InitSubSystem(SDL_INIT_JOYSTICK); + if (vid_gamepad_ok) + SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER); + + if (SDL_JoystickEventState (SDL_ENABLE) < 0) { + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); + return SCPE_IOERR; + } + + if (vid_gamepad_ok && SDL_GameControllerEventState (SDL_ENABLE) < 0) { + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); + return SCPE_IOERR; + } + + n = SDL_NumJoysticks(); + + for (i = 0; i < n; i++) { + if (vid_gamepad_ok && SDL_IsGameController (i)) { + SDL_GameController *x = SDL_GameControllerOpen (i); + if (x != NULL) { + sim_debug (SIM_VID_DBG_VIDEO, vid_dev, + "Game controller: %s\n", SDL_GameControllerNameForIndex(i)); + } + } + else { + y = SDL_JoystickOpen (i); + if (y != NULL) { + sim_debug (SIM_VID_DBG_VIDEO, vid_dev, + "Joystick: %s\n", SDL_JoystickNameForIndex(i)); + sim_debug (SIM_VID_DBG_VIDEO, vid_dev, + "Number of axes: %d, buttons: %d\n", + SDL_JoystickNumAxes(y), + SDL_JoystickNumButtons(y)); + } + } + } + + vid_gamepad_inited = 1; + return SCPE_OK; +} + t_stat vid_open (DEVICE *dptr, const char *title, uint32 width, uint32 height, int flags) { if (!vid_active) { @@ -538,10 +635,18 @@ if (!vid_active) { vid_dev = dptr; + memset (motion_callback, 0, sizeof motion_callback); + memset (button_callback, 0, sizeof button_callback); + stat = vid_create_window (); if (stat != SCPE_OK) return stat; + if (vid_init_controllers () != SCPE_OK) { + sim_debug (SIM_VID_DBG_VIDEO, vid_dev, + "vid_open() - Failed initializing game controllers\n"); + } + sim_debug (SIM_VID_DBG_VIDEO|SIM_VID_DBG_KEY|SIM_VID_DBG_MOUSE, vid_dev, "vid_open() - Success\n"); } return SCPE_OK; @@ -553,6 +658,12 @@ if (vid_active) { SDL_Event user_event; int status; + vid_gamepad_inited = 0; + memset (motion_callback, 0, sizeof motion_callback); + memset (button_callback, 0, sizeof button_callback); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); + vid_active = FALSE; if (vid_ready) { sim_debug (SIM_VID_DBG_VIDEO|SIM_VID_DBG_KEY|SIM_VID_DBG_MOUSE, vid_dev, "vid_close()\n"); @@ -1149,6 +1260,61 @@ switch (key) { } } +void vid_joy_motion (SDL_JoyAxisEvent *event) +{ + int n = sizeof motion_callback / sizeof (VID_GAMEPAD_CALLBACK); + int i; + + for (i = 0; i < n; i++) { + if (motion_callback[i]) { + motion_callback[i](event->which, event->axis, event->value); + } + } +} + +void vid_joy_button (SDL_JoyButtonEvent *event) +{ + int n = sizeof button_callback / sizeof (VID_GAMEPAD_CALLBACK); + int i; + + for (i = 0; i < n; i++) { + if (button_callback[i]) { + button_callback[i](event->which, event->button, event->state); + } + } +} + +void vid_controller_motion (SDL_ControllerAxisEvent *event) +{ + SDL_JoyAxisEvent e; + e.which = event->which; + e.axis = event->axis; + e.value = event->value; + vid_joy_motion (&e); +} + +void vid_controller_button (SDL_ControllerButtonEvent *event) +{ + /* SDL_GameControllerFromInstanceID is only available from SDL + version 2.0.4, so check the version at compile time. The + version is also checked at run time. */ +#if (SDL_MAJOR_VERSION > 2) || (SDL_MAJOR_VERSION == 2 && \ + (SDL_MINOR_VERSION > 0) || (SDL_PATCHLEVEL >= 4)) + + SDL_JoyButtonEvent e; + SDL_GameControllerButtonBind b; + SDL_GameController *c; + SDL_GameControllerButton button = (SDL_GameControllerButton)event->button; + + c = SDL_GameControllerFromInstanceID (event->which); + b = SDL_GameControllerGetBindForButton (c, button); + e.which = event->which; + e.button = b.value.button; + e.state = event->state; + vid_joy_button (&e); +#endif +} + void vid_key (SDL_KeyboardEvent *event) { SIM_KEY_EVENT ev; @@ -1691,17 +1857,36 @@ while (vid_active) { case SDL_KEYDOWN: case SDL_KEYUP: - vid_key ((SDL_KeyboardEvent*)&event); + vid_key (&event.key); break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: - vid_mouse_button ((SDL_MouseButtonEvent*)&event); + vid_mouse_button (&event.button); break; case SDL_MOUSEMOTION: - vid_mouse_move ((SDL_MouseMotionEvent*)&event); + vid_mouse_move (&event.motion); break; + + case SDL_JOYAXISMOTION: + vid_joy_motion (&event.jaxis); + break; + + case SDL_JOYBUTTONUP: + case SDL_JOYBUTTONDOWN: + vid_joy_button (&event.jbutton); + break; + + case SDL_CONTROLLERAXISMOTION: + vid_controller_motion (&event.caxis); + break; + + case SDL_CONTROLLERBUTTONUP: + case SDL_CONTROLLERBUTTONDOWN: + vid_controller_button (&event.cbutton); + break; + #if SDL_MAJOR_VERSION != 1 case SDL_WINDOWEVENT: if (event.window.windowID == vid_windowID) { diff --git a/sim_video.h b/sim_video.h index 47bb8bb..d1c0497 100644 --- a/sim_video.h +++ b/sim_video.h @@ -180,6 +180,9 @@ t_stat vid_open (DEVICE *dptr, const char *title, uint32 width, uint32 height, i /* code responsible for cursor display in video) */ typedef void (*VID_QUIT_CALLBACK)(void); t_stat vid_register_quit_callback (VID_QUIT_CALLBACK callback); +typedef void (*VID_GAMEPAD_CALLBACK)(int, int, int); +t_stat vid_register_gamepad_motion_callback (VID_GAMEPAD_CALLBACK); +t_stat vid_register_gamepad_button_callback (VID_GAMEPAD_CALLBACK); t_stat vid_close (void); t_stat vid_poll_kb (SIM_KEY_EVENT *ev); t_stat vid_poll_mouse (SIM_MOUSE_EVENT *ev);